#include <stdint.h>
#include <string.h>
#include <math.h>

#include "../core_memory/core_memory_private.h"
#include "../model_assets/xmodel_asset_private.h"
#include "../scripting/script_memory_private.h"
#include "coduo_engine_structs.h"
#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */
#include "dobj_private.h"
#include "xanim_private.h"

enum {
    DOBJ_DEFAULT_TRACE_REMAP_PAYLOAD_SIZE = 0x11,
    DOBJ_DEFAULT_TRACE_REMAP_STORAGE_BYTES = 0x14,
    DOBJ_DEFAULT_TRACE_REMAP_STRING_TYPE = 0x0c,
    DOBJ_PART_BIT_INDEX_SHIFT = 3,
    DOBJ_PART_BIT_INDEX_MASK = 7,
    DOBJ_PART_BIT_LOW_MASK = 0x01U,
    DOBJ_TRACE_REMAP_PAIR_STRIDE = 2,
    DOBJ_PART_QUAT_LANE_COUNT = 4
};

typedef struct coduo_dobj_model_part_layout_s {
    XModelPartsData *payload;
    XModelPartNameTable *partNameTable;
    uint8_t *parentPartDeltas;
    vec3_t *baseTranslations;
} coduo_dobj_model_part_layout_t;

/* NOT_FROM_ORIGINAL_SOURCE: resolves the nested XModel part-name payload. */
static coduo_dobj_model_part_layout_t coduomp_dobj_model_part_layout(
    XModel *model)
{
    XModelInfo *collision;
    fileData_t *partsEntry;
    XModelPartsData *payload;
    XModelPartNameTableSlot *tableSlot;
    coduo_dobj_model_part_layout_t layout;

    collision = model->info;
    partsEntry = collision->parts;
    payload = partsEntry->data.xmodelParts;
    tableSlot = payload->partNameTableSlot;

    layout.payload = payload;
    layout.partNameTable = tableSlot->partNameTable;
    layout.parentPartDeltas = tableSlot->parentPartDeltas;
    layout.baseTranslations = payload->baseTranslations;

    return layout;
}

/* NOT_FROM_ORIGINAL_SOURCE: byte-level access to packed DObj part bitsets. */
static uint8_t coduomp_dobj_part_bit_mask(int32_t partIndex)
{
    return (uint8_t)(1U << (partIndex & DOBJ_PART_BIT_INDEX_MASK));
}

/* NOT_FROM_ORIGINAL_SOURCE: byte-level access to packed DObj part bitsets. */
static qboolean coduomp_dobj_part_bit_is_set(const uint32_t *partBits,
                                             int32_t partIndex)
{
    const uint8_t *bytes = (const uint8_t *)partBits;

    return (bytes[partIndex >> DOBJ_PART_BIT_INDEX_SHIFT] &
            coduomp_dobj_part_bit_mask(partIndex)) != 0
               ? qtrue
               : qfalse;
}

/* NOT_FROM_ORIGINAL_SOURCE: byte-level access to packed DObj part bitsets. */
static qboolean coduomp_dobj_part_bit_byte_has_any(
    const uint32_t *partBits, int32_t partIndex)
{
    const uint8_t *bytes = (const uint8_t *)partBits;

    return bytes[partIndex >> DOBJ_PART_BIT_INDEX_SHIFT] != 0 ? qtrue
                                                              : qfalse;
}

/* NOT_FROM_ORIGINAL_SOURCE: byte-level access to packed DObj part bitsets. */
static void coduomp_dobj_part_bit_set(uint32_t *partBits,
                                     int32_t partIndex)
{
    uint8_t *bytes = (uint8_t *)partBits;
    int32_t byteIndex = partIndex >> DOBJ_PART_BIT_INDEX_SHIFT;

    bytes[byteIndex] =
        (uint8_t)(bytes[byteIndex] |
                  coduomp_dobj_part_bit_mask(partIndex));
}

/* NOT_FROM_ORIGINAL_SOURCE: typed copy between eval-part quaternion lanes. */
static void coduomp_dobj_eval_part_load_quat(const DObjAnimMat *part,
                                             vec4_t quat)
{
    quat[0] = part->primaryLane0;
    quat[1] = part->primaryLane1;
    quat[2] = part->primaryLane2;
    quat[3] = part->primaryLane3;
}

/* NOT_FROM_ORIGINAL_SOURCE: typed copy between eval-part quaternion lanes. */
static void coduomp_dobj_eval_part_store_quat(DObjAnimMat *part,
                                              const vec4_t quat)
{
    part->primaryLane0 = quat[0];
    part->primaryLane1 = quat[1];
    part->primaryLane2 = quat[2];
    part->primaryLane3 = quat[3];
}

/* NOT_FROM_ORIGINAL_SOURCE: typed copy between eval-part quaternion lanes. */
static void coduomp_dobj_eval_part_copy_quat(DObjAnimMat *dest,
                                             const DObjAnimMat *source)
{
    dest->primaryLane0 = source->primaryLane0;
    dest->primaryLane1 = source->primaryLane1;
    dest->primaryLane2 = source->primaryLane2;
    dest->primaryLane3 = source->primaryLane3;
}

/* NOT_FROM_ORIGINAL_SOURCE: typed copy between eval-part translation lanes. */
static void coduomp_dobj_eval_part_load_translation(
    const DObjAnimMat *part, vec3_t translation)
{
    translation[0] = part->secondaryLane0;
    translation[1] = part->secondaryLane1;
    translation[2] = part->secondaryLane2;
}

/* NOT_FROM_ORIGINAL_SOURCE: typed copy between eval-part translation lanes. */
static void coduomp_dobj_eval_part_store_translation(
    DObjAnimMat *part, const vec3_t translation)
{
    part->secondaryLane0 = translation[0];
    part->secondaryLane1 = translation[1];
    part->secondaryLane2 = translation[2];
}

/* NOT_FROM_ORIGINAL_SOURCE: typed copy between eval-part translation lanes. */
static void coduomp_dobj_eval_part_copy_translation(
    DObjAnimMat *dest, const DObjAnimMat *source)
{
    dest->secondaryLane0 = source->secondaryLane0;
    dest->secondaryLane1 = source->secondaryLane1;
    dest->secondaryLane2 = source->secondaryLane2;
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves the original remap-pair scan shape. */
static int32_t
coduomp_dobj_trace_remap_target_for_source(
    const DObjTracePartRemap *traceRemap, int32_t sourcePart)
{
    const uint8_t *pair =
        traceRemap->duplicatePairs;

    while (sourcePart != (int32_t)pair[0] - 1) {
        pair += DOBJ_TRACE_REMAP_PAIR_STRIDE;
    }

    return (int32_t)pair[1] - 1;
}

/* NOT_FROM_ORIGINAL_SOURCE: returns a duplicate target and advances on match. */
static qboolean coduomp_dobj_trace_remap_consume_source(
    const uint8_t **pairCursor,
    int32_t sourcePart, int32_t *targetPart)
{
    const uint8_t *pair = *pairCursor;

    if (sourcePart != (int32_t)pair[0] - 1) {
        return qfalse;
    }

    *targetPart = (int32_t)pair[1] - 1;
    *pairCursor = pair + DOBJ_TRACE_REMAP_PAIR_STRIDE;
    return qtrue;
}

/* NOT_FROM_ORIGINAL_SOURCE: routes quaternion multiply through recovered funcs. */
static void coduomp_dobj_eval_part_multiply_into_self(
    DObjAnimMat *part, const DObjAnimMat *rhsPart)
{
    vec4_t quat;
    vec4_t rhs;

    coduomp_dobj_eval_part_load_quat(part, quat);
    coduomp_dobj_eval_part_load_quat(rhsPart, rhs);
    DObjQuatMultiplyIntoFirst(quat, rhs);
    coduomp_dobj_eval_part_store_quat(part, quat);
}

/* NOT_FROM_ORIGINAL_SOURCE: routes quaternion multiply through recovered funcs. */
static void coduomp_dobj_eval_part_multiply_into_rhs(
    const DObjAnimMat *lhsPart, DObjAnimMat *part)
{
    vec4_t lhs;
    vec4_t quat;

    coduomp_dobj_eval_part_load_quat(lhsPart, lhs);
    coduomp_dobj_eval_part_load_quat(part, quat);
    DObjQuatMultiplyIntoSecond(lhs, quat);
    coduomp_dobj_eval_part_store_quat(part, quat);
}

/* NOT_FROM_ORIGINAL_SOURCE: expands quaternion lanes into a base-pose matrix. */
static void coduomp_dobj_eval_part_build_matrix(
    const DObjAnimMat *part, DObjSkelMat *matrix)
{
    vec4_t quat;

    coduomp_dobj_eval_part_load_quat(part, quat);
    DObjQuatToMatrix43(quat, matrix);
}

/* NOT_FROM_ORIGINAL_SOURCE: transforms eval-part translation in place. */
static void coduomp_dobj_eval_part_transform_translation(
    DObjAnimMat *part, const DObjSkelMat *matrix)
{
    vec3_t translation;

    coduomp_dobj_eval_part_load_translation(part, translation);
    DObjMatrixTransformVector43InPlace(translation, matrix);
    coduomp_dobj_eval_part_store_translation(part, translation);
}

/* NOT_FROM_ORIGINAL_SOURCE: writes matrix origin from eval translation lanes. */
static void coduomp_dobj_base_pose_store_origin(
    DObjSkelMat *matrix, const DObjAnimMat *part)
{
    matrix->origin[0] = part->secondaryLane0;
    matrix->origin[1] = part->secondaryLane1;
    matrix->origin[2] = part->secondaryLane2;
    matrix->origin[3] = 0.0f;
}

/* NOT_FROM_ORIGINAL_SOURCE: applies the model base translation for a child part. */
static void coduomp_dobj_eval_part_add_base_translation(
    DObjAnimMat *part, const vec3_t baseTranslation)
{
    part->secondaryLane0 += baseTranslation[0];
    part->secondaryLane1 += baseTranslation[1];
    part->secondaryLane2 += baseTranslation[2];
}

/* NOT_FROM_ORIGINAL_SOURCE: local stack-size calculation for trace remaps. */
static size_t coduomp_dobj_trace_remap_stack_size(DObj *state)
{
    size_t size;

    size = (size_t)state->partCount * 2U +
           CODUO_XANIM_PART_REMAP_PREFIX_SIZE + 1U;
    return (size + 15U) & ~15U;
}

DObjSkelMat *
DObjBasePoseForChild(DObj *state,
                     uint8_t childIndex)
{
    DObj *record;
    coduo_xanim_eval_storage_t *storage;

    record = state;
    storage = state->storage;

    return &storage->partSpans[record->childPartBaseIndices[childIndex]]
                .basePose;
}

void DObjGetBounds(DObj *state, vec3_t mins, vec3_t maxs)
{
    XModelGetBounds(state->childRefs[0], mins, maxs);
}

void XAnimInitSmallTreePool(void)
{
    char defaultTraceRemap[DOBJ_DEFAULT_TRACE_REMAP_STORAGE_BYTES];

    Com_Memset(defaultTraceRemap, 0, DOBJ_DEFAULT_TRACE_REMAP_STORAGE_BYTES);
    xanimDefaultPartRemapHandle =
        SL_GetStringOfLen(defaultTraceRemap, 0,
                                 DOBJ_DEFAULT_TRACE_REMAP_PAYLOAD_SIZE,
                                 DOBJ_DEFAULT_TRACE_REMAP_STRING_TYPE);
}

void XAnimShutdownSmallTreePool(void)
{
    if (xanimDefaultPartRemapHandle != 0) {
        SL_RemoveRefToStringOfLen(xanimDefaultPartRemapHandle,
                                  DOBJ_DEFAULT_TRACE_REMAP_PAYLOAD_SIZE);
        xanimDefaultPartRemapHandle = 0;
    }
}

void DObjDumpInfo(DObj *state)
{
    if (state == NULL) {
        Com_Printf("No Dobj\n");
        return;
    }

    Com_Printf("\nModels:\n");

    int32_t childPartBase = 0;
    for (int32_t childIndex = 0; childIndex < state->childCount;
         ++childIndex) {
        XModel *model = state->childRefs[childIndex];
        XModel *modelRecord = (XModel *)model;

        Com_Printf("%d: '%s'\n", childPartBase, modelRecord->name);
        childPartBase += XModelNumBones(model);
    }

    Com_Printf("\nBones:\n");
    for (int32_t boneIndex = 0; boneIndex < state->partCount; ++boneIndex) {
        Com_Printf("Bone %d: '%s'\n", boneIndex,
                   DObjGetBoneName(state, boneIndex));
    }

    if (state->tracePartRemapHandle == 0) {
        Com_Printf("\nNo part duplicates.\n");
    } else {
        const DObjTracePartRemap *partRemap =
            (const DObjTracePartRemap *)SL_ConvertToString(
                state->tracePartRemapHandle);

        Com_Printf("\nPart duplicates:\n");
        for (int32_t remapIndex = 0;
             partRemap->duplicatePairs[remapIndex] != 0; remapIndex += 2) {
            int32_t sourcePart =
                partRemap->duplicatePairs[remapIndex] - 1;
            int32_t targetPart =
                partRemap->duplicatePairs[remapIndex + 1] - 1;

            Com_Printf("%d ('%s') -> %d ('%s')\n", sourcePart,
                       DObjGetBoneName(state, sourcePart), targetPart,
                       DObjGetBoneName(state, targetPart));
        }
    }

    Com_Printf("\n");
}

void DObjBuildPartCollisionTable(DObj *state,
                                 XModelPartColl **partCollisions)
{
    for (int32_t childIndex = 0; childIndex < state->childCount;
         ++childIndex) {
        XModel *model = state->childRefs[childIndex];
        coduo_dobj_model_part_layout_t layout =
            coduomp_dobj_model_part_layout(model);
        XModelPartColl *modelParts = layout.payload->partCollisions;
        int32_t partCount =
            XModelNumBones(model);

        for (int32_t partIndex = 0; partIndex < partCount; ++partIndex) {
            *partCollisions = &modelParts[partIndex];
            ++partCollisions;
        }
    }
}

void DObjBuildTracePartRemap(DObj *state)
{
    DObj *record;
    size_t remapStackSize;
    uint32_t remapOffset;
    int32_t partBase;

    record = state;
    remapStackSize = coduomp_dobj_trace_remap_stack_size(state);
    uint8_t remap[remapStackSize];

    Com_Memset(remap, 0, CODUO_XANIM_PART_REMAP_PREFIX_SIZE);

    partBase = XModelNumBones(state->childRefs[0]);
    remapOffset = CODUO_XANIM_PART_REMAP_BYTES_OFFSET;

    for (int32_t childIndex = 1; childIndex < state->childCount;
         ++childIndex) {
        XModel *model;

        model = state->childRefs[childIndex];
        if (record->childParentPartIndices[childIndex] ==
            CODUO_DOBJ_CHILD_PARENT_PART_INDEX_NONE) {
            const uint16_t *partNames;
            int32_t partNameCount;
            qboolean remappedRootPart;

            partNames = XModelBoneNames(model);
            partNameCount = XModelNumBones(model);
            remappedRootPart = 0;

            for (int32_t localPart = 0; localPart < partNameCount;
                 ++localPart) {
                int32_t remapSource;
                int32_t remapTarget;

                remapSource = localPart + partBase;
                remapTarget = DObjFindPartIndex(state, partNames[localPart]);
                if (remapTarget != remapSource) {
                    if (localPart == 0) {
                        remappedRootPart = 1;
                    }

                    remap[remapOffset] = (uint8_t)(remapSource + 1);
                    remap[remapSource >> 3] =
                        (uint8_t)(remap[remapSource >> 3] |
                                  (1U << (remapSource & 7)));
                    ++remapOffset;
                    remap[remapOffset] = (uint8_t)(remapTarget + 1);
                    ++remapOffset;
                }
            }

            if (remappedRootPart == 0) {
                XModel *modelRecord =
                    (XModel *)model;
                XModel *rootModelRecord =
                    (XModel *)state->childRefs[0];

                Com_Printf("WARNING: Attempting to meld model, but root part "
                           "'%s' of model '%s' not found in model '%s' or any "
                           "of its descendants\n",
                           SL_ConvertToString(partNames[0]),
                           modelRecord->name, rootModelRecord->name);
            }
        }

        partBase += XModelNumBones(model);
    }

    if (remapOffset > CODUO_XANIM_PART_REMAP_BYTES_OFFSET) {
        remap[remapOffset] = 0;
        ++remapOffset;
        state->tracePartRemapHandle =
            SL_GetStringOfLen((const char *)remap, 0, remapOffset,
                              DOBJ_DEFAULT_TRACE_REMAP_STRING_TYPE);
    } else {
        state->tracePartRemapHandle = xanimDefaultPartRemapHandle;
    }
}

void DObjGetHierarchyBits(DObj *state,
                          int32_t boneIndex,
                          uint32_t *partBits)
{
    DObj *record;
    const DObjTracePartRemap *traceRemap;
    int32_t childBases[CODUO_DOBJ_CHILD_SLOT_COUNT];
    int32_t childIndex;

    record = state;
    for (int32_t wordIndex = 0;
         wordIndex < CODUO_XANIM_PART_BITSET_WORD_COUNT; ++wordIndex) {
        partBits[wordIndex] = 0;
    }

    if (state->tracePartRemapHandle == 0) {
        DObjBuildTracePartRemap(state);
    }

    traceRemap = (const DObjTracePartRemap *)SL_ConvertToString(
        state->tracePartRemapHandle);

    /* ORIGINAL_BINARY_BUG: signed authored bone counts are accumulated in
     * dword child bases without a lower-bound check. Preserve the original
     * representation and branch behavior for malformed XModels; see
     * original-bugs/coduomp-xmodel-loader-unbounded-input.md. */
    childBases[0] = 0;
    childIndex = 0;
    while (childIndex < state->childCount) {
        XModel *model = state->childRefs[childIndex];
        coduo_dobj_model_part_layout_t layout =
            coduomp_dobj_model_part_layout(model);
        int32_t childEnd =
            childBases[childIndex] + layout.partNameTable->count;

        if (boneIndex < childEnd) {
            break;
        }

        ++childIndex;
        if (childIndex == state->childCount) {
            return;
        }

        childBases[childIndex] = childEnd;
    }

    coduo_dobj_model_part_layout_t layout =
        coduomp_dobj_model_part_layout(state->childRefs[childIndex]);
    while (qtrue) {
        int32_t localPart = boneIndex - childBases[childIndex];

        while (qtrue) {
            coduomp_dobj_part_bit_set(partBits, boneIndex);

            if (coduomp_dobj_part_bit_is_set(traceRemap->sourcePartBits,
                                             boneIndex) != qfalse) {
                boneIndex = coduomp_dobj_trace_remap_target_for_source(
                    traceRemap, boneIndex);
            } else {
                int32_t parentDeltaIndex =
                    localPart - layout.payload->rootPartCount;

                if (parentDeltaIndex >= 0) {
                    boneIndex =
                        (int32_t)(boneIndex -
                                                  layout.parentPartDeltas
                                                      [parentDeltaIndex]);
                    break;
                }

                boneIndex = record->childParentPartIndices[childIndex];
                if (boneIndex == CODUO_DOBJ_CHILD_PARENT_PART_INDEX_NONE) {
                    return;
                }
            }

            do {
                --childIndex;
                if (childIndex < 0) {
                    return;
                }
                localPart = boneIndex - childBases[childIndex];
            } while (localPart < 0);

            layout =
                coduomp_dobj_model_part_layout(state->childRefs[childIndex]);
        }
    }
}

void DObjBackfillParentPartBits(DObj *state,
                                uint32_t *partBits)
{
    DObj *record;
    const DObjTracePartRemap *traceRemap;
    int32_t childBases[CODUO_DOBJ_CHILD_SLOT_COUNT];
    int32_t boneIndex;
    int32_t childIndex;

    record = state;
    boneIndex = state->partCount - 1;

    if (state->tracePartRemapHandle == 0) {
        DObjBuildTracePartRemap(state);
    }

    traceRemap = (const DObjTracePartRemap *)SL_ConvertToString(
        state->tracePartRemapHandle);

    /* ORIGINAL_BINARY_BUG: as in DObjGetHierarchyBits, these are signed
     * dword accumulators fed by unchecked authored bone counts. See
     * original-bugs/coduomp-xmodel-loader-unbounded-input.md. */
    childBases[0] = 0;
    childIndex = 0;
    while (qtrue) {
        XModel *model = state->childRefs[childIndex];
        coduo_dobj_model_part_layout_t layout =
            coduomp_dobj_model_part_layout(model);
        int32_t childEnd =
            childBases[childIndex] + layout.partNameTable->count;

        if (boneIndex < childEnd) {
            break;
        }

        ++childIndex;
        childBases[childIndex] = childEnd;
    }

    coduo_dobj_model_part_layout_t layout =
        coduomp_dobj_model_part_layout(state->childRefs[childIndex]);
    while (qtrue) {
        int32_t localPart;

        do {
            localPart = boneIndex - childBases[childIndex];
            if (localPart >= 0) {
                break;
            }

            --childIndex;
            if (childIndex < 0) {
                return;
            }
            layout =
                coduomp_dobj_model_part_layout(state->childRefs[childIndex]);
        } while (qtrue);

        /*
         * Objdump at VA 0x080c5b0e tests the containing byte first, then
         * falls through for every bit except bit zero. Preserve that exact
         * machine behavior even though a normal single-bit test would read
         * cleaner.
         */
        if (coduomp_dobj_part_bit_byte_has_any(partBits, boneIndex) ==
                qfalse &&
            (coduomp_dobj_part_bit_mask(boneIndex) &
             DOBJ_PART_BIT_LOW_MASK) != 0) {
            --boneIndex;
            continue;
        }

        int32_t parentPart;
        if (coduomp_dobj_part_bit_is_set(traceRemap->sourcePartBits,
                                         boneIndex) != qfalse) {
            parentPart = coduomp_dobj_trace_remap_target_for_source(
                traceRemap, boneIndex);
        } else {
            int32_t parentDeltaIndex =
                localPart - layout.payload->rootPartCount;

            if (parentDeltaIndex >= 0) {
                parentPart =
                    (int32_t)(boneIndex -
                                              layout.parentPartDeltas
                                                  [parentDeltaIndex]);
            } else {
                parentPart = record->childParentPartIndices[childIndex];
                if (parentPart ==
                    CODUO_DOBJ_CHILD_PARENT_PART_INDEX_NONE) {
                    --boneIndex;
                    continue;
                }
            }
        }

        coduomp_dobj_part_bit_set(partBits, parentPart);
        --boneIndex;
    }
}

void DObjCalcSkel(DObj *state, const uint32_t *partBits)
{
    DObj *record = state;
    coduo_xanim_eval_storage_t *storage = state->storage;
    uint32_t blockedOrNotRequestedBits[CODUO_XANIM_PART_BITSET_WORD_COUNT];
    uint32_t buildPartBits[CODUO_XANIM_PART_BITSET_WORD_COUNT];
    uint32_t skipQuatBits[CODUO_XANIM_PART_BITSET_WORD_COUNT];
    qboolean allPartsAlreadyBlocked = qtrue;

    for (int32_t wordIndex = 0;
         wordIndex < CODUO_XANIM_PART_BITSET_WORD_COUNT; ++wordIndex) {
        blockedOrNotRequestedBits[wordIndex] =
            ~partBits[wordIndex] | storage->blockedPartBits[wordIndex];
        if (blockedOrNotRequestedBits[wordIndex] != UINT32_MAX) {
            allPartsAlreadyBlocked = qfalse;
        }
    }

    if (allPartsAlreadyBlocked != qfalse) {
        return;
    }

    if (state->tracePartRemapHandle == 0) {
        DObjBuildTracePartRemap(state);
    }

    const DObjTracePartRemap *traceRemap =
        (const DObjTracePartRemap *)SL_ConvertToString(
            state->tracePartRemapHandle);

    for (int32_t wordIndex = 0;
         wordIndex < CODUO_XANIM_PART_BITSET_WORD_COUNT; ++wordIndex) {
        storage->blockedPartBits[wordIndex] |= partBits[wordIndex];
        buildPartBits[wordIndex] =
            ~blockedOrNotRequestedBits[wordIndex] &
            storage->controlPartBits[wordIndex];
        skipQuatBits[wordIndex] =
            blockedOrNotRequestedBits[wordIndex] | buildPartBits[wordIndex] |
            traceRemap->sourcePartBits[wordIndex];
    }

    for (int32_t wordIndex = 0;
         wordIndex < CODUO_XANIM_PART_BITSET_WORD_COUNT; ++wordIndex) {
        buildPartBits[wordIndex] =
            ~skipQuatBits[wordIndex] | buildPartBits[wordIndex];
    }

    DObjSkelMat *matrices = &storage->partSpans[0].basePose;
    DObjAnimMat *evalParts = DObjEvalStorageSeedOutput(state);
    DObjAnimMat *evalPart = evalParts;
    int32_t globalPart = 0;
    const uint8_t *remapPair =
        traceRemap->duplicatePairs;

    for (int32_t childIndex = 0; childIndex < state->childCount;
         ++childIndex) {
        coduo_dobj_model_part_layout_t layout =
            coduomp_dobj_model_part_layout(state->childRefs[childIndex]);
        uint8_t parentPart =
            record->childParentPartIndices[childIndex];

        if (parentPart == CODUO_DOBJ_CHILD_PARENT_PART_INDEX_NONE) {
            for (int32_t remaining = layout.payload->rootPartCount;
                 remaining != 0; --remaining) {
                int32_t duplicatePart;

                if (coduomp_dobj_part_bit_is_set(buildPartBits,
                                                 globalPart) == qfalse &&
                    coduomp_dobj_trace_remap_consume_source(
                        &remapPair, globalPart, &duplicatePart) != qfalse &&
                    coduomp_dobj_part_bit_is_set(blockedOrNotRequestedBits,
                                                 globalPart) == qfalse) {
                    coduomp_dobj_eval_part_copy_quat(
                        evalPart, &evalParts[duplicatePart]);
                }

                ++evalPart;
                ++globalPart;
            }
        } else {
            DObjAnimMat *parentEvalPart =
                &evalParts[parentPart];

            for (int32_t remaining = layout.payload->rootPartCount;
                 remaining != 0; --remaining) {
                if (coduomp_dobj_part_bit_is_set(skipQuatBits,
                                                 globalPart) == qfalse) {
                    coduomp_dobj_eval_part_multiply_into_self(
                        evalPart, parentEvalPart);
                } else if (coduomp_dobj_part_bit_is_set(buildPartBits,
                                                        globalPart) !=
                           qfalse) {
                    coduomp_dobj_eval_part_multiply_into_rhs(parentEvalPart,
                                                            evalPart);
                }

                ++evalPart;
                ++globalPart;
            }
        }

        uint8_t *parentDeltas = layout.parentPartDeltas;
        int32_t nonRootPartCount =
            layout.partNameTable->count - layout.payload->rootPartCount;

        /* ORIGINAL_BINARY_BUG: stock treats this signed derived count in both
         * skeleton passes as a nonzero dword countdown. See original-bugs/
         * coduomp-xmodel-loader-unbounded-input.md. */
        int32_t localPart = 0;
        while (nonRootPartCount != 0) {
            uint8_t parentDelta = parentDeltas[localPart];
            int32_t duplicatePart;

            if (coduomp_dobj_part_bit_is_set(skipQuatBits,
                                             globalPart) == qfalse) {
                coduomp_dobj_eval_part_multiply_into_self(
                    evalPart, &evalParts[globalPart - parentDelta]);
            } else if (coduomp_dobj_part_bit_is_set(buildPartBits,
                                                    globalPart) == qfalse) {
                if (coduomp_dobj_trace_remap_consume_source(
                        &remapPair, globalPart, &duplicatePart) != qfalse &&
                    coduomp_dobj_part_bit_is_set(blockedOrNotRequestedBits,
                                                 globalPart) == qfalse) {
                    coduomp_dobj_eval_part_copy_quat(
                        evalPart, &evalParts[duplicatePart]);
                }
            } else {
                coduomp_dobj_eval_part_multiply_into_rhs(
                    &evalParts[globalPart - parentDelta], evalPart);
            }

            ++evalPart;
            ++globalPart;
            ++localPart;
            --nonRootPartCount;
        }
    }

    evalPart = evalParts;
    DObjSkelMat *matrix = matrices;
    globalPart = 0;
    remapPair = traceRemap->duplicatePairs;

    for (int32_t childIndex = 0; childIndex < state->childCount;
         ++childIndex) {
        coduo_dobj_model_part_layout_t layout =
            coduomp_dobj_model_part_layout(state->childRefs[childIndex]);
        uint8_t parentPart =
            record->childParentPartIndices[childIndex];

        if (parentPart == CODUO_DOBJ_CHILD_PARENT_PART_INDEX_NONE) {
            for (int32_t remaining = layout.payload->rootPartCount;
                 remaining != 0; --remaining) {
                int32_t duplicatePart;

                if (coduomp_dobj_part_bit_is_set(buildPartBits,
                                                 globalPart) == qfalse) {
                    if (coduomp_dobj_trace_remap_consume_source(
                            &remapPair, globalPart,
                            &duplicatePart) != qfalse &&
                        coduomp_dobj_part_bit_is_set(
                            blockedOrNotRequestedBits,
                            globalPart) == qfalse) {
                        coduomp_dobj_eval_part_copy_translation(
                            evalPart, &evalParts[duplicatePart]);
                        *matrix = matrices[duplicatePart];
                    }
                } else {
                    coduomp_dobj_eval_part_build_matrix(evalPart, matrix);
                    coduomp_dobj_base_pose_store_origin(matrix, evalPart);
                }

                ++evalPart;
                ++matrix;
                ++globalPart;
            }
        } else {
            DObjSkelMat *parentMatrix = &matrices[parentPart];

            for (int32_t remaining = layout.payload->rootPartCount;
                 remaining != 0; --remaining) {
                if (coduomp_dobj_part_bit_is_set(buildPartBits,
                                                 globalPart) != qfalse) {
                    coduomp_dobj_eval_part_build_matrix(evalPart, matrix);
                    coduomp_dobj_eval_part_transform_translation(
                        evalPart, parentMatrix);
                    coduomp_dobj_base_pose_store_origin(matrix, evalPart);
                }

                ++evalPart;
                ++matrix;
                ++globalPart;
            }
        }

        uint8_t *parentDeltas = layout.parentPartDeltas;
        vec3_t *baseTranslations = layout.baseTranslations;
        int32_t nonRootPartCount =
            layout.partNameTable->count - layout.payload->rootPartCount;

        int32_t localPart = 0;
        while (nonRootPartCount != 0) {
            uint8_t parentDelta = parentDeltas[localPart];
            int32_t duplicatePart;

            if (coduomp_dobj_part_bit_is_set(buildPartBits,
                                             globalPart) == qfalse) {
                if (coduomp_dobj_trace_remap_consume_source(
                        &remapPair, globalPart, &duplicatePart) != qfalse &&
                    coduomp_dobj_part_bit_is_set(blockedOrNotRequestedBits,
                                                 globalPart) == qfalse) {
                    coduomp_dobj_eval_part_copy_translation(
                        evalPart, &evalParts[duplicatePart]);
                    *matrix = matrices[duplicatePart];
                }
            } else {
                coduomp_dobj_eval_part_build_matrix(evalPart, matrix);
                coduomp_dobj_eval_part_add_base_translation(
                    evalPart, baseTranslations[localPart]);
                coduomp_dobj_eval_part_transform_translation(
                    evalPart, &matrices[globalPart - parentDelta]);
                coduomp_dobj_base_pose_store_origin(matrix, evalPart);
            }

            ++evalPart;
            ++matrix;
            ++globalPart;
            ++localPart;
            --nonRootPartCount;
        }
    }
}

void DObjConstructRecord(DObjModel *models,
                         uint16_t modelCount,
                         XAnimTree *runtimeTree,
                         DObj *state,
                         uint16_t gameId)
{
    uint8_t childCount;
    int32_t partCount;

    state->runtimeTree = runtimeTree;
    state->storage = 0;
    state->skelCacheKey = 0;
    state->rootHandle = gameId;
    state->tracePartRemapHandle = 0;
    state->childSkipMask = 0;

    if (runtimeTree != NULL) {
        XAnim *sourceTree;
        uint8_t *partRemapTableBytes;
        uint8_t *generation;

        sourceTree = runtimeTree->sourceTree;
        partRemapTableBytes = coduo_engine_xanim_part_remap_table_bytes(
            runtimeTree, runtimeTree->partRemapTableSelector);
        state->partRemapTableBytes = partRemapTableBytes;

        generation = coduo_engine_xanim_part_remap_generation_bytes(
            partRemapTableBytes, sourceTree->nodeCount);
        ++*generation;
        if (*generation == 0) {
            *generation = 1;
            Com_Memset(generation + 1, 0, sourceTree->nodeCount);
        }

        runtimeTree->partRemapTableSelector =
            1 - runtimeTree->partRemapTableSelector;
    } else {
        state->partRemapTableBytes = 0;
    }

    childCount = 0;
    partCount = 0;
    for (int32_t modelIndex = 0; modelIndex < modelCount;
         ++modelIndex, ++models) {
        XModel *model;

        model = models->model;
        state->childRefs[childCount] = model;
        state->childParentPartIndices[childCount] =
            CODUO_DOBJ_CHILD_PARENT_PART_INDEX_NONE;
        state->childModelIndices[childCount] = models->negativeModelIndex;
        state->childPartBaseIndices[childCount] =
            (uint8_t)partCount;

        if (models->ignoreCollision != 0) {
            state->childSkipMask |= 1U << modelIndex;
        }

        if (modelIndex != 0 && models->tagName != NULL) {
            const char *tagName;

            tagName = models->tagName;
            if (tagName[0] != '\0') {
                uint16_t tagHandle;

                tagHandle = SL_FindLowercaseString(tagName);
                if (tagHandle != 0) {
                    for (int32_t parent = 0; parent < childCount; ++parent) {
                        int32_t localPart;

                        localPart =
                            XModelGetBoneIndex(state->childRefs[parent],
                                               tagHandle);
                        if (localPart >= 0) {
                            state->childParentPartIndices[childCount] =
                                (uint8_t)(
                                    localPart +
                                    state->childPartBaseIndices[parent]);
                            goto update_part_count;
                        }
                    }
                }

                XModel *rootModelRecord =
                    (XModel *)state->childRefs[0];

                Com_Printf("WARNING: Part '%s' not found in model '%s' or any "
                           "of its descendants\n",
                           tagName, rootModelRecord->name);
            }
        }

update_part_count:
        /* ORIGINAL_BINARY_BUG: stock rejects only totals greater than 127.
         * Negative signed XModel bone counts pass this test and are narrowed
         * into the byte-sized DObj fields below. See
         * original-bugs/coduomp-xmodel-loader-unbounded-input.md. */
        partCount += XModelNumBones(model);
        if (partCount > CODUO_DOBJ_PART_INDEX_MAX) {
            XModel *rootModelRecord =
                (XModel *)state->childRefs[0];

            Com_Error(1, "\x15" "dobj for xmodel '%s' has more than %d bones",
                      rootModelRecord->name, CODUO_DOBJ_PART_INDEX_MAX);
            goto finish;
        }

        ++childCount;
    }

finish:
    state->childCount = childCount;
    state->partCount = (uint8_t)partCount;
}

void DObjDestroyRecord(DObj *state, qboolean releaseRuntimeTree)
{
    XAnimTree *runtimeTree;

    runtimeTree = state->runtimeTree;
    if (runtimeTree != NULL) {
        if (releaseRuntimeTree != 0) {
            XAnimClearTree(runtimeTree);
        }

        if (state->partRemapTableBytes ==
            coduo_engine_xanim_part_remap_table_bytes(runtimeTree, 0)) {
            runtimeTree->partRemapTableSelector = 0;
        } else {
            runtimeTree->partRemapTableSelector = 1;
        }

        state->partRemapTableBytes = 0;
        state->runtimeTree = 0;
    }

    if (state->tracePartRemapHandle != 0) {
        if (state->tracePartRemapHandle != xanimDefaultPartRemapHandle) {
            const char *payload;

            payload =
                SL_ConvertToString(state->tracePartRemapHandle);
            SL_RemoveRefToStringOfLen(
                state->tracePartRemapHandle,
                strlen(payload + CODUO_XANIM_PART_REMAP_PREFIX_SIZE) +
                    CODUO_XANIM_PART_REMAP_PREFIX_SIZE + 1U);
        }

        state->tracePartRemapHandle = 0;
    }
}

void DObjTraceParts(DObj *state, const vec3_t start,
                    const vec3_t end, const uint8_t *partState,
                    coduo_dobj_trace_result_t *trace)
{
    DObj *record;
    DObjSkelMat *basePose;
    const uint8_t *traceRemap;
    /* ORIGINAL_BINARY_BUG: 0x080c7649..0x080c765c indexes this 128-word
     * stack table with the unchecked cumulative part index.  Malformed
     * signed child counts can keep DObj construction's final total at or
     * below 127 while later positive children together contribute more than
     * 128 iterations.  Preserved; see
     * original-bugs/coduomp-xmodel-loader-unbounded-input.md. */
    uint16_t remappedPartStateIndices[CODUO_XANIM_PART_BITSET_MAX_BITS];
    vec3_t delta;
    float invLenSq;
    uint32_t bestPriority;
    int32_t globalPart;

    record = state;
    delta[0] = end[0] - start[0];
    delta[1] = end[1] - start[1];
    delta[2] = end[2] - start[2];
    /* 1.0 / (delta.delta) kept 80-bit, one store -> shim. */
#if EMULATE_X87
    invLenSq = x87f_store_f32(x87f_div(
        x87f_load_f32(1.0f),
        x87f_add(x87f_add(x87f_mul(x87f_load_f32(delta[0]), x87f_load_f32(delta[0])),
                          x87f_mul(x87f_load_f32(delta[1]), x87f_load_f32(delta[1]))),
                 x87f_mul(x87f_load_f32(delta[2]), x87f_load_f32(delta[2])))));
#else
    invLenSq =
        1.0f / (delta[0] * delta[0] + delta[1] * delta[1] +
                delta[2] * delta[2]);
#endif
    basePose = DObjBasePoseForChild(state, 0);

    trace->surfaceFlags = 0;
    trace->startsolid = 0;
    trace->allsolid = 0;
    trace->hitPartNameHandle = 0;
    trace->hitPartStateIndex = 0;
    trace->normal[2] = 0.0f;
    trace->normal[1] = 0.0f;
    trace->normal[0] = 0.0f;

    bestPriority = 2;
    traceRemap =
        (const uint8_t *)SL_ConvertToString(
            state->tracePartRemapHandle) +
        CODUO_XANIM_PART_REMAP_PREFIX_SIZE;
    globalPart = 0;

    for (int32_t childIndex = 0; childIndex < state->childCount;
         ++childIndex) {
        XModel *model;
        coduo_dobj_model_part_layout_t layout;
        const uint16_t *partNames;
        XModelPartColl *parts;
        const uint8_t *partStateMap;
        int32_t partNameCount;
        int32_t parentThreshold;
        uint32_t childSkipped;

        model = state->childRefs[childIndex];
        layout = coduomp_dobj_model_part_layout(model);
        /*
         * Original DObjTraceParts reads the i386 xmodelparts payload at
         * +0x00/+0x04/+0x08/+0x14. Resolve those semantic fields through the
         * host payload so widened pointers do not shift collision metadata.
         */
        partNames = layout.partNameTable->handles;
        partNameCount = layout.partNameTable->count;
        parts = layout.payload->partCollisions;
        partStateMap = layout.payload->partStateIndices;
        parentThreshold = layout.payload->rootPartCount;
        childSkipped = (1U << (childIndex & 31)) & state->childSkipMask;

        for (int32_t localPart = 0; localPart < partNameCount;
             ++localPart, ++globalPart, ++basePose) {
            uint16_t partStateIndex;
            uint32_t partPriority;

            partStateIndex = partStateMap[localPart];
            partPriority = partState[partStateIndex];

            if (globalPart == (int32_t)traceRemap[0] - 1) {
                const uint8_t *remapEntry;

                remapEntry = traceRemap;
                traceRemap += 2;
                if (partPriority == 1) {
                    partStateIndex =
                        remappedPartStateIndices[remapEntry[1] - 1];
                    partPriority = partState[partStateIndex];
                }
            } else if (partPriority == 1) {
                if (localPart < parentThreshold) {
                    uint8_t parentPart;

                    parentPart = record->childParentPartIndices[childIndex];
                    if (parentPart == CODUO_DOBJ_CHILD_PARENT_PART_INDEX_NONE) {
                        partStateIndex = 0;
                    } else {
                        partStateIndex = remappedPartStateIndices[parentPart];
                    }
                } else {
                    uint8_t parentBack;

                    parentBack =
                        layout.parentPartDeltas[localPart - parentThreshold];
                    partStateIndex =
                        remappedPartStateIndices[globalPart - parentBack];
                }
                partPriority = partState[partStateIndex];
            }

            remappedPartStateIndices[globalPart] = partStateIndex;
            if (childSkipped != 0) {
                continue;
            }

            XModelPartColl *part = &parts[localPart];
            if (part->radiusSq == 0.0f || bestPriority > partPriority) {
                continue;
            }

            vec3_t center;
            vec3_t startToCenter;
            float projection;
            float distanceSq;
            float radiusDelta;

            DObjMatrixTransformVector43(part->center, basePose, center);
            startToCenter[0] = start[0] - center[0];
            startToCenter[1] = start[1] - center[1];
            startToCenter[2] = start[2] - center[2];
            /* -(startToCenter.delta) * invLenSq kept 80-bit, one store -> shim. */
#if EMULATE_X87
            projection = x87f_store_f32(x87f_mul(
                x87f_neg(x87f_add(x87f_add(
                    x87f_mul(x87f_load_f32(startToCenter[0]), x87f_load_f32(delta[0])),
                    x87f_mul(x87f_load_f32(startToCenter[1]), x87f_load_f32(delta[1]))),
                    x87f_mul(x87f_load_f32(startToCenter[2]), x87f_load_f32(delta[2])))),
                x87f_load_f32(invLenSq)));
#else
            projection =
                -(startToCenter[0] * delta[0] +
                  startToCenter[1] * delta[1] +
                  startToCenter[2] * delta[2]) *
                invLenSq;
#endif

            if (projection < 1.0f) {
                if (projection > 0.0f) {
                    vec3_t nearest;

                    /* nearest[k] = startToCenter[k] + delta[k]*projection MA;
                     * nearest.nearest dot -> shim. */
#if EMULATE_X87
                    for (int32_t k = 0; k < 3; k++) {
                        nearest[k] = x87f_store_f32(x87f_add(
                            x87f_mul(x87f_load_f32(delta[k]), x87f_load_f32(projection)),
                            x87f_load_f32(startToCenter[k])));
                    }
                    distanceSq = x87f_store_f32(x87f_add(x87f_add(
                        x87f_mul(x87f_load_f32(nearest[0]), x87f_load_f32(nearest[0])),
                        x87f_mul(x87f_load_f32(nearest[1]), x87f_load_f32(nearest[1]))),
                        x87f_mul(x87f_load_f32(nearest[2]), x87f_load_f32(nearest[2]))));
#else
                    nearest[0] = startToCenter[0] + delta[0] * projection;
                    nearest[1] = startToCenter[1] + delta[1] * projection;
                    nearest[2] = startToCenter[2] + delta[2] * projection;
                    distanceSq = nearest[0] * nearest[0] +
                                 nearest[1] * nearest[1] +
                                 nearest[2] * nearest[2];
#endif
                } else {
#if EMULATE_X87
                    distanceSq = x87f_store_f32(x87f_add(x87f_add(
                        x87f_mul(x87f_load_f32(startToCenter[0]), x87f_load_f32(startToCenter[0])),
                        x87f_mul(x87f_load_f32(startToCenter[1]), x87f_load_f32(startToCenter[1]))),
                        x87f_mul(x87f_load_f32(startToCenter[2]), x87f_load_f32(startToCenter[2]))));
#else
                    distanceSq = startToCenter[0] * startToCenter[0] +
                                 startToCenter[1] * startToCenter[1] +
                                 startToCenter[2] * startToCenter[2];
#endif
                }
            } else {
                vec3_t endToCenter;

                endToCenter[0] = end[0] - center[0];
                endToCenter[1] = end[1] - center[1];
                endToCenter[2] = end[2] - center[2];
#if EMULATE_X87
                distanceSq = x87f_store_f32(x87f_add(x87f_add(
                    x87f_mul(x87f_load_f32(endToCenter[0]), x87f_load_f32(endToCenter[0])),
                    x87f_mul(x87f_load_f32(endToCenter[1]), x87f_load_f32(endToCenter[1]))),
                    x87f_mul(x87f_load_f32(endToCenter[2]), x87f_load_f32(endToCenter[2]))));
#else
                distanceSq = endToCenter[0] * endToCenter[0] +
                             endToCenter[1] * endToCenter[1] +
                             endToCenter[2] * endToCenter[2];
#endif
            }

            radiusDelta = part->radiusSq - distanceSq;
            if (radiusDelta <= 0.0f) {
                continue;
            }

            if (bestPriority == partPriority) {
                /*
                 * 0x080c783b..0x080c784c: only the sqrt result is rounded to
                 * float; the projection - sqrt difference stays in an x87
                 * register for the compare, so it must not pass through a
                 * float local here.
                 */
#if EMULATE_X87
                if (x87f_le(
                        x87f_load_f32(trace->fraction),
                        x87f_sub(x87f_load_f32(projection),
                                 x87f_load_f32((float)sqrt(
                                     (double)(radiusDelta * invLenSq)))))) {
                    continue;
                }
#else
                if (projection -
                        (float)sqrt((double)(radiusDelta * invLenSq)) >=
                    trace->fraction) {
                    continue;
                }
#endif
            }

            vec3_t localStart;
            vec3_t localEnd;
            float enter;
            float exit;
            qboolean startInside;
            qboolean endInside;
            float hitSign;
            int32_t hitAxis;
            float sign;
            const float *bounds;

            DObjMatrixInverseTransformVector43(start, basePose, localStart);
            DObjMatrixInverseTransformVector43(end, basePose, localEnd);
            enter = 0.0f;
            exit = trace->fraction;
            startInside = 1;
            endInside = 1;
            hitSign = -1.0f;
            hitAxis = -1;
            sign = -1.0f;
            bounds = part->mins;

            while (1) {
                for (int32_t axis = 0; axis < 3; ++axis) {
                    float startDist;
                    float endDist;

                    startDist = (localStart[axis] - bounds[axis]) * sign;
                    endDist = (localEnd[axis] - bounds[axis]) * sign;
                    if (startDist > 0.0f) {
                        float denom;

                        if (endDist > 0.0f) {
                            goto next_part;
                        }
                        startInside = 0;
                        denom = startDist - endDist;
                        if (enter * denom < startDist) {
                            enter = startDist / denom;
                            if (exit <= enter) {
                                goto next_part;
                            }
                            hitSign = sign;
                            hitAxis = axis;
                        }
                    } else if (endDist > 0.0f) {
                        float denom;

                        endInside = 0;
                        denom = startDist - endDist;
                        if (exit * denom < startDist) {
                            exit = startDist / denom;
                            if (exit <= enter) {
                                goto next_part;
                            }
                        }
                    }
                }

                if (sign == 1.0f) {
                    break;
                }
                sign = 1.0f;
                bounds = part->maxs;
            }

            if (startInside == 0) {
                if (bestPriority == partPriority) {
                    if (trace->fraction <= enter) {
                        goto next_part;
                    }
                } else {
                    bestPriority = partPriority;
                }

                trace->fraction = enter;
                trace->hitPartNameHandle = partNames[localPart];
                trace->hitPartStateIndex = partStateIndex;
                trace->normal[0] = basePose->axis[hitAxis][0] * hitSign;
                trace->normal[1] = basePose->axis[hitAxis][1] * hitSign;
                trace->normal[2] = basePose->axis[hitAxis][2] * hitSign;
            } else {
                trace->startsolid = 1;
                if (endInside != 0) {
                    trace->allsolid = 1;
                    trace->fraction = 0.0f;
                    trace->hitPartNameHandle = partNames[localPart];
                    trace->hitPartStateIndex = partStateIndex;
                    trace->normal[2] = 0.0f;
                    trace->normal[1] = 0.0f;
                    trace->normal[0] = 0.0f;
                    return;
                }
            }

next_part:;
        }
    }
}

void DObjTraceModelParts(DObj *state, const vec3_t start,
                         const vec3_t end,
                         int32_t contentMask,
                         coduo_dobj_trace_result_t *trace)
{
    DObjSkelMat *basePose;
    trace_t traceState;

    basePose = DObjBasePoseForChild(state, 0);
    trace->hitPartNameHandle = 0;
    trace->hitPartStateIndex = 0;
    trace->startsolid = 0;
    trace->allsolid = 0;

    traceState.fraction = trace->fraction;
    traceState.normal[0] = 0.0f;
    traceState.normal[1] = 0.0f;
    traceState.normal[2] = 0.0f;
    traceState.surfaceFlags = 0;

    for (int32_t childIndex = 0; childIndex < state->childCount;
         ++childIndex) {
        XModel *model;
        const uint16_t *partNames;
        int32_t hitPart;

        model = state->childRefs[childIndex];
        partNames = XModelBoneNames(model);
        hitPart =
            XModelTraceLine(model, &traceState, basePose, start, end,
                            contentMask);
        if (hitPart >= 0) {
            trace->hitPartNameHandle = partNames[hitPart];
        }

        basePose += XModelNumBones(model);
    }

    trace->fraction = traceState.fraction;
    trace->surfaceFlags = traceState.surfaceFlags;
    trace->normal[0] = traceState.normal[0];
    trace->normal[1] = traceState.normal[1];
    trace->normal[2] = traceState.normal[2];
}
