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

#include "../core_memory/core_memory_private.h"
#include "../core_runtime/core_runtime_private.h"
#include "../animation/xanim_private.h"
#include "coduo_engine_structs.h"
#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */
#include "script_memory_private.h"
#include "script_runtime_private.h"
#include "script_serialization_private.h"

#define XANIM_SERVER_TIME_NOTIFY_EPSILON 0.001f
#define XANIM_ROOT_NODE_INDEX 0
#define XANIM_DELTA_EVAL_ROOT_WEIGHT 1.0f
#define XANIM_DELTA_ROTATION_IDENTITY_LANE0 0.0f
#define XANIM_DELTA_ROTATION_IDENTITY_LANE1 1.0f
#define XANIM_WEIGHT_EPSILON 0.001f
#define XANIM_COM_ERROR_DROP 1
#define XANIM_SET_GOAL_WEIGHT_OK 0
#define XANIM_SET_GOAL_WEIGHT_TREE_COMPLETE 1
#define XANIM_SET_GOAL_WEIGHT_NOTIFY_SOURCE_MISSING 2
#define XANIM_PART_REMAP_TABLE_COUNT 2
#define XANIM_SYNC_FLAG_LOOPING 0x0001U
#define XANIM_SYNC_FLAG_NONLOOPING 0x0002U
#define XANIM_SYNC_FLAG_MASK 0x0003U
#define XANIM_PARENT_NOTIFY_SOURCE_FLAG 0x0004U
#define XANIM_END_TIME 1.0f

enum {
    XANIM_DELTA_ROTATION_LANE0 = 0,
    XANIM_DELTA_ROTATION_LANE1 = 1,
    XANIM_DELTA_TRANSLATION_WEIGHT = 2,
    XANIM_DELTA_TRANSLATION_LANE0 = 3,
    XANIM_DELTA_TRANSLATION_LANE1 = 4,
    XANIM_DELTA_TRANSLATION_LANE2 = 5,
    XANIM_DELTA_COMPONENT_COUNT = 6
};

static void XAnimCalcDeltaTree(XAnimTree *runtimeTree,
                               uint32_t nodeIndex, float weight,
                               float *delta,
                               qboolean clear, qboolean normalize);
long double XAnimFloatAbs(float value);

/* NOT_FROM_ORIGINAL_SOURCE: local loaded-record access for leaf entries. */
static XAnimParts *
coduo_engine_xanim_script_entry_loaded_record(XAnimEntry *entry)
{
    return entry->payload.leafAsset->data.xanimParts;
}

const char *XAnimGetAnimTreeDebugName(XAnim *tree)
{
    return tree->name;
}

/* NOT_FROM_ORIGINAL_SOURCE: syscall float arguments arrive as raw int bits. */
static float coduo_engine_xanim_weight_from_bits(int32_t bits)
{
    float value;

    memcpy(&value, &bits, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: syscall float arguments arrive as raw int bits. */
static float coduo_engine_xanim_blend_time_from_bits(int32_t bits)
{
    float value;

    memcpy(&value, &bits, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: syscall float arguments arrive as raw int bits. */
static float coduo_engine_xanim_rate_from_bits(int32_t bits)
{
    float value;

    memcpy(&value, &bits, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: shared output formatting for XAnim delta callers. */
static void coduo_engine_xanim_store_delta_output(
    const float *delta, float *rotationDelta, vec3_t moveDelta,
    qboolean identityOnEitherZero)
{
    qboolean useIdentity =
        identityOnEitherZero
            ? (delta[XANIM_DELTA_ROTATION_LANE0] == 0.0f ||
               delta[XANIM_DELTA_ROTATION_LANE1] == 0.0f)
            : (delta[XANIM_DELTA_ROTATION_LANE0] == 0.0f &&
               delta[XANIM_DELTA_ROTATION_LANE1] == 0.0f);

    if (useIdentity) {
        rotationDelta[0] = XANIM_DELTA_ROTATION_IDENTITY_LANE0;
        rotationDelta[1] = XANIM_DELTA_ROTATION_IDENTITY_LANE1;
    } else {
        rotationDelta[0] = delta[XANIM_DELTA_ROTATION_LANE0];
        rotationDelta[1] = delta[XANIM_DELTA_ROTATION_LANE1];
    }

    moveDelta[0] = delta[XANIM_DELTA_TRANSLATION_LANE0];
    moveDelta[1] = delta[XANIM_DELTA_TRANSLATION_LANE1];
    moveDelta[2] = delta[XANIM_DELTA_TRANSLATION_LANE2];
}

/* NOT_FROM_ORIGINAL_SOURCE: local clear for the six XAnim delta lanes. */
static void coduo_engine_xanim_clear_delta(float *delta)
{
    delta[XANIM_DELTA_ROTATION_LANE0] = 0.0f;
    delta[XANIM_DELTA_ROTATION_LANE1] = 0.0f;
    delta[XANIM_DELTA_TRANSLATION_WEIGHT] = 0.0f;
    delta[XANIM_DELTA_TRANSLATION_LANE0] = 0.0f;
    delta[XANIM_DELTA_TRANSLATION_LANE1] = 0.0f;
    delta[XANIM_DELTA_TRANSLATION_LANE2] = 0.0f;
}

/* NOT_FROM_ORIGINAL_SOURCE: local selector for current-vs-target delta weight. */
static float coduo_engine_xanim_selected_pool_weight(XAnimState *slot)
{
    return xanim_evalPoolWeightSelector == 0 ? slot->currentWeight
                                             : slot->targetWeight;
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for XAnimCalcDeltaTree. */
static void coduo_engine_xanim_normalize_delta_translation(float *delta)
{
    if (delta[XANIM_DELTA_TRANSLATION_WEIGHT] != 0.0f) {
        float scale = 1.0f / delta[XANIM_DELTA_TRANSLATION_WEIGHT];

        delta[XANIM_DELTA_TRANSLATION_LANE0] *= scale;
        delta[XANIM_DELTA_TRANSLATION_LANE1] *= scale;
        delta[XANIM_DELTA_TRANSLATION_LANE2] *= scale;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for XAnimCalcDeltaTree. */
/* x87 shim helpers: 2-comp dot, a/b divide, and d += a*b MA each kept 80-bit
 * with one float store, matching stock; the #else forms are byte-identical to
 * the original expressions. */
#if EMULATE_X87
#define XA_DOT2(a, b, c, d)                                                   \
    x87f_store_f32(x87f_add(                                                  \
        x87f_mul(x87f_load_f32(a), x87f_load_f32(b)),                         \
        x87f_mul(x87f_load_f32(c), x87f_load_f32(d))))
#define XA_DIV(a, b)                                                          \
    x87f_store_f32(x87f_div(x87f_load_f32(a), x87f_load_f32(b)))
#define XA_MADD(dst, a, b)                                                    \
    ((dst) = x87f_store_f32(x87f_add(                                         \
         x87f_mul(x87f_load_f32(a), x87f_load_f32(b)), x87f_load_f32(dst))))
#else
#define XA_DOT2(a, b, c, d) ((a) * (b) + (c) * (d))
#define XA_DIV(a, b) ((a) / (b))
#define XA_MADD(dst, a, b) ((dst) += (a) * (b))
#endif

/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of the weighted merge
 * branch in XAnimCalcDeltaTree. */
static void coduo_engine_xanim_merge_weighted_delta(float *dest,
                                                     float *source,
                                                     float weight)
{
    float rotationLengthSq =
        XA_DOT2(source[XANIM_DELTA_ROTATION_LANE1],
                source[XANIM_DELTA_ROTATION_LANE1],
                source[XANIM_DELTA_ROTATION_LANE0],
                source[XANIM_DELTA_ROTATION_LANE0]);
    if (rotationLengthSq != 0.0f) {
        float scale = XA_DIV(weight, (float)sqrt((double)rotationLengthSq));

        XA_MADD(dest[XANIM_DELTA_ROTATION_LANE0],
                source[XANIM_DELTA_ROTATION_LANE0], scale);
        XA_MADD(dest[XANIM_DELTA_ROTATION_LANE1],
                source[XANIM_DELTA_ROTATION_LANE1], scale);
    }

    if (source[XANIM_DELTA_TRANSLATION_WEIGHT] != 0.0f) {
        float scale =
            XA_DIV(weight, source[XANIM_DELTA_TRANSLATION_WEIGHT]);

        dest[XANIM_DELTA_TRANSLATION_WEIGHT] +=
            weight; /* single add -> native */
        XA_MADD(dest[XANIM_DELTA_TRANSLATION_LANE0],
                source[XANIM_DELTA_TRANSLATION_LANE0], scale);
        XA_MADD(dest[XANIM_DELTA_TRANSLATION_LANE1],
                source[XANIM_DELTA_TRANSLATION_LANE1], scale);
        XA_MADD(dest[XANIM_DELTA_TRANSLATION_LANE2],
                source[XANIM_DELTA_TRANSLATION_LANE2], scale);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for XAnimCalcDeltaTree. */
static void coduo_engine_xanim_scale_weighted_delta(float *delta,
                                                    float weight)
{
    float rotationLengthSq =
        XA_DOT2(delta[XANIM_DELTA_ROTATION_LANE1],
                delta[XANIM_DELTA_ROTATION_LANE1],
                delta[XANIM_DELTA_ROTATION_LANE0],
                delta[XANIM_DELTA_ROTATION_LANE0]);
    if (rotationLengthSq != 0.0f) {
        float scale = XA_DIV(weight, (float)sqrt((double)rotationLengthSq));

        /* *= scale is a single mul -> native. */
        delta[XANIM_DELTA_ROTATION_LANE0] *= scale;
        delta[XANIM_DELTA_ROTATION_LANE1] *= scale;
    }

    if (delta[XANIM_DELTA_TRANSLATION_WEIGHT] != 0.0f) {
        float scale =
            XA_DIV(weight, delta[XANIM_DELTA_TRANSLATION_WEIGHT]);

        delta[XANIM_DELTA_TRANSLATION_WEIGHT] = weight;
        delta[XANIM_DELTA_TRANSLATION_LANE0] *= scale;
        delta[XANIM_DELTA_TRANSLATION_LANE1] *= scale;
        delta[XANIM_DELTA_TRANSLATION_LANE2] *= scale;
    }
}

#undef XA_DOT2
#undef XA_DIV
#undef XA_MADD

/* NOT_FROM_ORIGINAL_SOURCE: local leaf case for XAnimCalcDeltaTree. */
static void coduo_engine_xanim_calc_leaf_delta(XAnimTree *runtimeTree,
                                               uint32_t nodeIndex,
                                               float weight, float *delta,
                                               qboolean clear)
{
    if (clear) {
        coduo_engine_xanim_clear_delta(delta);
    }

    XAnimEntry *entry =
        &runtimeTree->sourceTree->entries[nodeIndex];
    XAnimParts *loadedRecord =
        entry->payload.leafAsset->data.xanimParts;
    if (loadedRecord->hasDeltaMotion == 0) {
        return;
    }

    uint16_t handle =
        runtimeTree->poolNodeHandles[nodeIndex];
    if (handle == 0) {
        return;
    }

    XAnimState *slot =
        &xanim_pool[handle].states[xanim_activePoolPayloadSlot];
    if (xanim_evalLeafOutputMode != 0) {
        XAnimCalcAbsDeltaParts(loadedRecord, weight, delta, slot->time);
        return;
    }

    XAnimCalcRelDeltaParts(loadedRecord, weight, delta,
                           slot->oldTime, slot->time);
}

static void XAnimCalcDeltaTree(XAnimTree *runtimeTree,
                               uint32_t nodeIndex, float weight,
                               float *delta,
                               qboolean clear, qboolean normalize)
{
    XAnimEntry *entry =
        &runtimeTree->sourceTree->entries[nodeIndex];

    if (entry->childCount == 0) {
        coduo_engine_xanim_calc_leaf_delta(runtimeTree, nodeIndex, weight,
                                           delta, clear);
        return;
    }

    int32_t firstActiveChild = -1;
    float firstWeight = 0.0f;
    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        int32_t childIndex = entry->payload.parent.firstChildIndex + child;
        uint16_t handle =
            runtimeTree->poolNodeHandles[childIndex];

        if (handle != 0) {
            float noteTrack = coduo_engine_xanim_selected_pool_weight(
                &xanim_pool[handle].states[xanim_activePoolPayloadSlot]);

            if (noteTrack != 0.0f) {
                firstActiveChild = child;
                firstWeight = noteTrack;
                break;
            }
        }
    }

    if (firstActiveChild < 0) {
        if (clear) {
            coduo_engine_xanim_clear_delta(delta);
        }
        return;
    }

    int32_t secondActiveChild = -1;
    float secondWeight = 0.0f;
    for (int32_t child = firstActiveChild + 1;
         child < (int32_t)entry->childCount; ++child) {
        int32_t childIndex = entry->payload.parent.firstChildIndex + child;
        uint16_t handle =
            runtimeTree->poolNodeHandles[childIndex];

        if (handle != 0) {
            float noteTrack = coduo_engine_xanim_selected_pool_weight(
                &xanim_pool[handle].states[xanim_activePoolPayloadSlot]);

            if (noteTrack != 0.0f) {
                secondActiveChild = child;
                secondWeight = noteTrack;
                break;
            }
        }
    }

    if (secondActiveChild < 0) {
        XAnimCalcDeltaTree(runtimeTree,
                           entry->payload.parent.firstChildIndex +
                               (uint32_t)firstActiveChild,
                           weight, delta, clear, normalize);
        return;
    }

    float scratch[XANIM_DELTA_COMPONENT_COUNT];
    float *blendDelta = clear ? delta : scratch;
    XAnimCalcDeltaTree(runtimeTree,
                       entry->payload.parent.firstChildIndex +
                           (uint32_t)firstActiveChild,
                       firstWeight, blendDelta, qtrue, qtrue);
    XAnimCalcDeltaTree(runtimeTree,
                       entry->payload.parent.firstChildIndex +
                           (uint32_t)secondActiveChild,
                       secondWeight, blendDelta, qfalse, qtrue);

    for (int32_t child = secondActiveChild + 1;
         child < (int32_t)entry->childCount; ++child) {
        int32_t childIndex = entry->payload.parent.firstChildIndex + child;
        uint16_t handle =
            runtimeTree->poolNodeHandles[childIndex];

        if (handle != 0) {
            float noteTrack = coduo_engine_xanim_selected_pool_weight(
                &xanim_pool[handle].states[xanim_activePoolPayloadSlot]);

            if (noteTrack != 0.0f) {
                XAnimCalcDeltaTree(runtimeTree, (uint32_t)childIndex,
                                   noteTrack, blendDelta, qfalse, qtrue);
            }
        }
    }

    if (normalize == qfalse) {
        coduo_engine_xanim_normalize_delta_translation(delta);
    } else if (clear == qfalse) {
        coduo_engine_xanim_merge_weighted_delta(delta, blendDelta, weight);
    } else {
        coduo_engine_xanim_scale_weighted_delta(delta, weight);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: typed accessor for the recovered DObj layout. */
XAnimTree *coduo_engine_xanim_get_runtime_tree(DObj *state)
{
    return state->runtimeTree;
}

long double XAnimGetLength(XAnim *tree, int32_t animIndex)
{
    XAnimEntry *entry = &tree->entries[animIndex];
    XAnimParts *record = entry->payload.leafAsset->data.xanimParts;

    return (long double)record->frameCountMinusOne / record->frameRate;
}

#if EMULATE_X87
/* NOT_FROM_ORIGINAL_SOURCE: Apple Silicon C long double is binary64, whereas
 * original i386 XAnimGetLength (0x080bef1f..0x080bef2d) returns the
 * bare fild/fdiv result in 80-bit st(0). Native callers that keep that result
 * live across more x87 arithmetic use this carrier rather than silently
 * narrowing the original return value. */
x87f coduo_engine_xanim_get_length_x87(XAnim *tree,
                                       int32_t animIndex)
{
    XAnimEntry *entry = &tree->entries[animIndex];
    XAnimParts *record = entry->payload.leafAsset->data.xanimParts;

    return x87f_div(x87f_load_i32(record->frameCountMinusOne),
                    x87f_load_f32(record->frameRate));
}
#endif

float XAnimGetTime(XAnimTree *runtimeTree, int32_t animIndex)
{
    uint16_t handle =
        runtimeTree->poolNodeHandles[animIndex];

    if (handle == 0) {
        return 0.0f;
    }

    return xanim_pool[handle]
        .states[xanim_activePoolPayloadSlot]
        .time;
}

float XAnimGetWeight(XAnimTree *runtimeTree, int32_t animIndex)
{
    uint16_t handle =
        runtimeTree->poolNodeHandles[animIndex];

    if (handle == 0) {
        return 0.0f;
    }

    return xanim_pool[handle]
        .states[xanim_activePoolPayloadSlot]
        .currentWeight;
}

qboolean XAnimHasFinished(XAnimTree *runtimeTree,
                          int32_t animIndex)
{
    uint16_t handle =
        runtimeTree->poolNodeHandles[animIndex];

    if (handle == 0) {
        return qtrue;
    }

    XAnimState *slot =
        &xanim_pool[handle].states[xanim_activePoolPayloadSlot];

    if (slot->time < slot->oldTime || slot->time == 1.0f) {
        return qtrue;
    }

    return (int16_t)slot->oldCycleCount < (int16_t)slot->cycleCount ? qtrue
                                                                     : qfalse;
}

int32_t XAnimGetNumChildren(XAnim *tree, int32_t animIndex)
{
    return tree->entries[animIndex].childCount;
}

int32_t XAnimGetChildAt(XAnim *tree, int32_t animIndex,
                        int32_t childIndex)
{
    return (int32_t)tree->entries[animIndex].payload.parent.firstChildIndex +
           childIndex;
}

void DObjInitServerTime(DObj *state,
                        float serverTime)
{
    if (state->runtimeTree == NULL) {
        return;
    }

    xanim_evalTime = serverTime;
    xanim_currentTree = state->runtimeTree;
    XAnimUpdateServerInfoSyncInternal(XANIM_ROOT_NODE_INDEX);
}

void DObjUpdateClientInfo(DObj *state, float serverTime)
{
    xanim_deferredNotifyCount = 0;

    if (state->runtimeTree == NULL) {
        return;
    }

    xanim_evalRootHandle = state->rootHandle;
    xanim_evalTime = serverTime;
    xanim_currentTree = state->runtimeTree;
    XAnimUpdateClientInfoInternal(XANIM_ROOT_NODE_INDEX, serverTime, qtrue);
}

qboolean DObjUpdateServerInfo(DObj *state,
                              float serverTime,
                              qboolean notify)
{
    if (state->runtimeTree == NULL) {
        return qfalse;
    }

    xanim_evalRootHandle = state->rootHandle;
    xanim_currentTree = state->runtimeTree;
    if (notify == qfalse) {
        XAnimUpdateServerInfoInternal(XANIM_ROOT_NODE_INDEX, serverTime,
                                     qfalse);
        return qfalse;
    }

    float notifyFraction =
        XAnimFindServerNoteTrack(XANIM_ROOT_NODE_INDEX, serverTime);
    float notifyWindowEnd =
        serverTime * notifyFraction + XANIM_SERVER_TIME_NOTIFY_EPSILON;

    if (notifyFraction == 1.0f || !(serverTime >= notifyWindowEnd)) {
        XAnimUpdateServerInfoInternal(XANIM_ROOT_NODE_INDEX, serverTime,
                                     qtrue);
        return qfalse;
    }

    XAnimUpdateServerInfoInternal(XANIM_ROOT_NODE_INDEX, notifyWindowEnd,
                                 qtrue);
    return qtrue;
}

int32_t DObjGetClientNotifyList(xanim_deferred_notify_t **notifies)
{
    *notifies = xanim_deferredNotifies;
    return xanim_deferredNotifyCount;
}

XAnimInfo *
XAnimAllocInfo(XAnimTree *runtimeTree,
               int32_t nodeIndex)
{
    uint16_t handle = xanim_pool[0].freeNext;

    if (handle == 0) {
        Com_Error(XANIM_COM_ERROR_DROP,
                  "\x15" "exceeded maximum number of anim info");
        return NULL;
    }

    xanim_poolUsedCount++;
    if (xanim_poolHighWaterCount < xanim_poolUsedCount) {
        xanim_poolHighWaterCount = xanim_poolUsedCount;
    }

    runtimeTree->activePoolNodeCount++;
    xanim_pool[0].freeNext = xanim_pool[handle].freeNext;
    xanim_pool[xanim_pool[0].freeNext].freePrev = 0;
    runtimeTree->poolNodeHandles[nodeIndex] = handle;

    return &xanim_pool[handle];
}

qboolean XAnimHasEffectiveParentWeight(
    XAnimTree *runtimeTree, int32_t nodeIndex)
{
    while (nodeIndex != XANIM_ROOT_NODE_INDEX) {
        nodeIndex = runtimeTree->sourceTree->entries[nodeIndex].parentIndex;
        uint16_t handle =
            runtimeTree->poolNodeHandles[nodeIndex];

        if (xanim_pool[handle]
                .states[xanim_activePoolPayloadSlot]
                .currentWeight == 0.0f) {
            return qfalse;
        }
    }

    return qtrue;
}

qboolean XAnimHasEffectiveChildWeight(XAnimTree *runtimeTree,
                                     int32_t nodeIndex)
{
    uint16_t handle =
        runtimeTree->poolNodeHandles[nodeIndex];

    if (handle == 0 ||
        xanim_pool[handle]
                .states[xanim_activePoolPayloadSlot]
                .currentWeight == 0.0f) {
        return qfalse;
    }

    XAnimEntry *entry = &runtimeTree->sourceTree->entries[nodeIndex];
    if (entry->childCount == 0) {
        return qtrue;
    }

    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        if (XAnimHasEffectiveChildWeight(
                runtimeTree, entry->payload.parent.firstChildIndex + child)) {
            return qtrue;
        }
    }

    return qfalse;
}

qboolean XAnimHasEffectiveWeight(XAnimTree *runtimeTree,
                                int32_t nodeIndex)
{
    return XAnimHasEffectiveParentWeight(runtimeTree, nodeIndex) &&
           XAnimHasEffectiveChildWeight(runtimeTree, nodeIndex);
}

void XAnimClearGoalWeight(XAnimTree *runtimeTree,
                          int32_t nodeIndex,
                          float blendTime)
{
    uint16_t handle =
        runtimeTree->poolNodeHandles[nodeIndex];

    if (handle == 0) {
        return;
    }

    XAnimInfo *node = &xanim_pool[handle];
    XAnimState *slot =
        &node->states[xanim_activePoolPayloadSlot];

    if (!XAnimHasEffectiveWeight(runtimeTree, nodeIndex)) {
        slot->currentWeight = 0.0f;
        slot->weightBlendTimeRemaining = 0.0f;
        slot->targetWeight = 0.0f;
    } else if (slot->targetWeight != 0.0f) {
        slot->targetWeight = 0.0f;
        slot->weightBlendTimeRemaining = blendTime;
    } else if (blendTime < slot->weightBlendTimeRemaining) {
        slot->weightBlendTimeRemaining = blendTime;
    }

    if (xanim_activePoolPayloadSlot != 0) {
        XAnimClearServerNotify(node);
    }
}

void XAnimClearTreeGoalWeights_r(XAnimTree *runtimeTree,
                                int32_t nodeIndex,
                                float blendTime)
{
    if (runtimeTree->poolNodeHandles[nodeIndex] == 0) {
        return;
    }

    XAnimClearGoalWeight(runtimeTree, nodeIndex, blendTime);

    XAnimEntry *entry = &runtimeTree->sourceTree->entries[nodeIndex];
    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        XAnimClearTreeGoalWeights_r(
            runtimeTree, entry->payload.parent.firstChildIndex + child,
            blendTime);
    }
}

void XAnimClearTreeGoalWeights(XAnimTree *runtimeTree,
                               int32_t nodeIndex,
                               float blendTime)
{
    if (blendTime < XANIM_WEIGHT_EPSILON) {
        blendTime = 0.0f;
    }

    XAnimClearTreeGoalWeights_r(runtimeTree, nodeIndex, blendTime);
}

void XAnimClearTreeGoalWeightsStrict(XAnimTree *runtimeTree,
                                     int32_t nodeIndex,
                                     float blendTime)
{
    if (blendTime < XANIM_WEIGHT_EPSILON) {
        blendTime = 0.0f;
    }

    XAnimEntry *entry = &runtimeTree->sourceTree->entries[nodeIndex];
    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        XAnimClearTreeGoalWeights_r(
            runtimeTree, entry->payload.parent.firstChildIndex + child,
            blendTime);
    }
}

void XAnimClearGoalWeightKnobInternal(XAnimTree *runtimeTree,
                                     int32_t nodeIndex,
                                     float weight,
                                     float blendTime)
{
    if (nodeIndex == XANIM_ROOT_NODE_INDEX) {
        return;
    }

    XAnimEntry *parent =
        &runtimeTree->sourceTree
             ->entries[runtimeTree->sourceTree->entries[nodeIndex].parentIndex];
    float maxWeight = 0.0f;

    for (int32_t child = 0; child < (int32_t)parent->childCount; ++child) {
        int32_t childIndex = parent->payload.parent.firstChildIndex + child;
        uint16_t handle =
            runtimeTree->poolNodeHandles[childIndex];
        float currentWeight = 0.0f;

        if (handle != 0) {
            currentWeight = xanim_pool[handle]
                                .states[xanim_activePoolPayloadSlot]
                                .currentWeight;
        }

        float siblingWeight =
            childIndex == nodeIndex
                ? (float)XAnimFloatAbs(weight - currentWeight)
                : currentWeight;
        if (maxWeight < siblingWeight) {
            maxWeight = siblingWeight;
        }
    }

    float siblingBlendTime = maxWeight * blendTime;
    if (siblingBlendTime < XANIM_WEIGHT_EPSILON) {
        siblingBlendTime = 0.0f;
    }

    for (int32_t child = 0; child < (int32_t)parent->childCount; ++child) {
        int32_t childIndex = parent->payload.parent.firstChildIndex + child;

        if (childIndex != nodeIndex) {
            XAnimClearGoalWeight(runtimeTree, childIndex, siblingBlendTime);
        }
    }
}

void XAnimInitServerTime(XAnimInfo *node)
{
    XAnimState *slot =
        &node->states[XANIM_SECONDARY_POOL_PAYLOAD_SLOT];

    slot->time = 0.0f;
    slot->cycleCount = 0;
    slot->oldTime = 0.0f;
    slot->oldCycleCount = 0;
}

void XAnimInitClientTime(XAnimInfo *node)
{
    XAnimState *slot =
        &node->states[XANIM_PRIMARY_POOL_PAYLOAD_SLOT];

    slot->time = 0.0f;
    slot->cycleCount = 0;
    slot->oldTime = 0.0f;
    slot->oldCycleCount = 0;
}

void XAnimSetAnimRateInternal(int32_t nodeIndex, float rate)
{
    uint16_t handle =
        xanim_currentTree->poolNodeHandles[nodeIndex];

    xanim_pool[handle]
        .states[xanim_activePoolPayloadSlot]
        .rateScale = rate;
}

void XAnimFillInSyncNodes_r(XAnim *tree, int32_t nodeIndex,
                            qboolean requireLooping)
{
    XAnimEntry *entry = &tree->entries[nodeIndex];

    if (entry->childCount == 0) {
        XAnimParts *loadedRecord =
            coduo_engine_xanim_script_entry_loaded_record(entry);
        if (loadedRecord->looped != requireLooping) {
            if (requireLooping == qfalse) {
                Com_Error(
                    XANIM_COM_ERROR_DROP,
                    "\x15" "animation '%s' in '%s' cannot be sync nonlooping and looping",
                    entry->payload.leafAsset->name,
                    XAnimGetAnimTreeDebugName(tree));
            } else {
                Com_Error(
                    XANIM_COM_ERROR_DROP,
                    "\x15" "animation '%s' in '%s' cannot be sync looping and nonlooping",
                    entry->payload.leafAsset->name,
                    XAnimGetAnimTreeDebugName(tree));
            }
        }
        return;
    }

    if ((entry->payload.parent.flags & XANIM_SYNC_FLAG_MASK) != 0) {
        int32_t depth = 0;
        XAnimEntry *cursor = entry;

        do {
            ++depth;
            cursor = &tree->entries[cursor->payload.parent.firstChildIndex];
        } while (cursor->childCount != 0);

        Com_Error(
            XANIM_COM_ERROR_DROP,
            "\x15" "duplicate specification of animation sync in '%s', %d nodes above '%s'",
            XAnimGetAnimTreeDebugName(tree), depth,
            cursor->payload.leafAsset->name);
    }

    entry->payload.parent.flags |= requireLooping != qfalse
                                         ? XANIM_SYNC_FLAG_LOOPING
                                         : XANIM_SYNC_FLAG_NONLOOPING;
    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        XAnimFillInSyncNodes_r(
            tree, entry->payload.parent.firstChildIndex + child,
            requireLooping);
    }
}

void XAnimSetupSyncNodes_r(XAnim *tree, int32_t nodeIndex)
{
    XAnimEntry *entry = &tree->entries[nodeIndex];

    if (entry->childCount == 0) {
        return;
    }

    uint16_t syncFlags = entry->payload.parent.flags & XANIM_SYNC_FLAG_MASK;
    if (syncFlags == 0) {
        for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
            XAnimSetupSyncNodes_r(
                tree, entry->payload.parent.firstChildIndex + child);
        }
        return;
    }

    if (syncFlags == XANIM_SYNC_FLAG_MASK) {
        Com_Error(XANIM_COM_ERROR_DROP,
                  "\x15" "animation cannot be sync looping and sync nonlooping");
    }

    entry->payload.parent.flags |= XANIM_PARENT_NOTIFY_SOURCE_FLAG;
    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        XAnimFillInSyncNodes_r(
            tree, entry->payload.parent.firstChildIndex + child,
            syncFlags == XANIM_SYNC_FLAG_LOOPING);
    }
}

void XAnimSetupSyncNodes(XAnim *tree)
{
    XAnimSetupSyncNodes_r(tree, XANIM_ROOT_NODE_INDEX);
}

qboolean XAnimHasTime(XAnim *tree, int32_t animIndex)
{
    XAnimEntry *entry = &tree->entries[animIndex];

    return entry->childCount == 0 ||
           (entry->payload.parent.flags & XANIM_SYNC_FLAG_MASK) != 0
               ? qtrue
               : qfalse;
}

qboolean XAnimIsPrimitive(XAnim *tree, int32_t animIndex)
{
    return tree->entries[animIndex].childCount == 0 ? qtrue : qfalse;
}

void XAnimSetTime(XAnimTree *runtimeTree, int32_t animIndex,
                  float time)
{
    uint16_t handle =
        runtimeTree->poolNodeHandles[animIndex];

    if (handle == 0) {
        return;
    }

    volatile XAnimEntry *entry =
        &runtimeTree->sourceTree->entries[animIndex];
    (void)entry;

    XAnimState *slot =
        &xanim_pool[handle].states[xanim_activePoolPayloadSlot];
    slot->time = time;
    slot->cycleCount = 0;
    slot->oldTime = time;
    slot->oldCycleCount = 0;
}

void XAnimCopyTimes(XAnimInfo *source,
                    XAnimInfo *dest)
{
    XAnimState *sourceSlot =
        &source->states[xanim_activePoolPayloadSlot];
    XAnimState *destSlot =
        &dest->states[xanim_activePoolPayloadSlot];

    destSlot->time = sourceSlot->time;
    destSlot->cycleCount = sourceSlot->cycleCount;
    destSlot->oldTime = sourceSlot->oldTime;
    destSlot->oldCycleCount = sourceSlot->oldCycleCount;
}

void XAnimUpdateServerNotify(int32_t nodeIndex)
{
    uint16_t handle =
        xanim_currentTree->poolNodeHandles[nodeIndex];
    XAnimInfo *node = &xanim_pool[handle];

    if (node->notifyName == 0) {
        return;
    }

    XAnimState *secondary =
        &node->states[XANIM_SECONDARY_POOL_PAYLOAD_SLOT];
    if (secondary->time == XANIM_END_TIME) {
        node->notifyIndex = -1;
        return;
    }

    XAnimEntry *entry =
        &xanim_currentTree->sourceTree->entries[nodeIndex];
    if (entry->childCount != 0) {
        if (node->notifyChildIndex == 0) {
            return;
        }

        entry = &xanim_currentTree->sourceTree->entries[node->notifyChildIndex];
    }

    node->notifyIndex =
        (int16_t)XAnimGetNextNotifyTime(entry, node,
                                          secondary->time);
}

void XAnimUpdateSyncTimeChildren(int32_t nodeIndex,
                                 XAnimInfo *source)
{
    XAnimEntry *entry =
        &xanim_currentTree->sourceTree->entries[nodeIndex];

    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        int32_t childIndex = entry->payload.parent.firstChildIndex + child;
        uint16_t handle =
            xanim_currentTree->poolNodeHandles[childIndex];

        if (handle == 0) {
            continue;
        }

        XAnimCopyTimes(source, &xanim_pool[handle]);
        if (xanim_activePoolPayloadSlot != 0) {
            XAnimUpdateServerNotify(childIndex);
        }
        XAnimUpdateSyncTimeChildren(childIndex, source);
    }
}

void XAnimUpdateSyncTime(int32_t nodeIndex, qboolean restart)
{
    int32_t syncNodeIndex = nodeIndex;

    while (syncNodeIndex != XANIM_ROOT_NODE_INDEX) {
        uint16_t handle =
            xanim_currentTree->poolNodeHandles[syncNodeIndex];
        XAnimInfo *node = &xanim_pool[handle];
        XAnimEntry *entry =
            &xanim_currentTree->sourceTree->entries[syncNodeIndex];

        if (entry->childCount != 0 &&
            (entry->payload.parent.flags &
             XANIM_PARENT_NOTIFY_SOURCE_FLAG) != 0) {
            if (restart != qfalse ||
                !XAnimHasEffectiveWeight(xanim_currentTree, syncNodeIndex)) {
                if (xanim_activePoolPayloadSlot == 0) {
                    XAnimInitClientTime(node);
                } else {
                    XAnimInitServerTime(node);
                }
            }

            XAnimUpdateSyncTimeChildren(nodeIndex, node);
            while (nodeIndex != syncNodeIndex) {
                XAnimCopyTimes(
                    node, &xanim_pool[xanim_currentTree
                                          ->poolNodeHandles[nodeIndex]]);
                if (xanim_activePoolPayloadSlot != 0) {
                    XAnimUpdateServerNotify(nodeIndex);
                }
                nodeIndex =
                    xanim_currentTree->sourceTree->entries[nodeIndex]
                        .parentIndex;
            }
            return;
        }

        syncNodeIndex = entry->parentIndex;
    }

    XAnimEntry *entry =
        &xanim_currentTree->sourceTree->entries[nodeIndex];
    if (entry->childCount == 0 &&
        (restart != qfalse ||
         !XAnimHasEffectiveWeight(xanim_currentTree, nodeIndex))) {
        XAnimInfo *node =
            &xanim_pool[xanim_currentTree->poolNodeHandles[nodeIndex]];
        if (xanim_activePoolPayloadSlot == 0) {
            XAnimInitClientTime(node);
        } else {
            XAnimInitServerTime(node);
        }
    }
}

int32_t XAnimGetDescendantWithGreatestWeight(int32_t nodeIndex)
{
    XAnimEntry *entry =
        &xanim_currentTree->sourceTree->entries[nodeIndex];

    if (entry->childCount == 0) {
        return nodeIndex;
    }

    float bestWeight = 0.0f;
    int32_t bestDescendant = 0;

    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        int32_t childIndex = entry->payload.parent.firstChildIndex + child;
        uint16_t handle =
            xanim_currentTree->poolNodeHandles[childIndex];

        XAnimState *slot =
            &xanim_pool[handle].states[xanim_activePoolPayloadSlot];
        if (!(slot->targetWeight <= bestWeight)) {
            int32_t descendant =
                XAnimGetDescendantWithGreatestWeight(childIndex);

            if (descendant != 0) {
                bestWeight = slot->targetWeight;
                bestDescendant = descendant;
            }
        }
    }

    return bestDescendant;
}

int32_t XAnimSetGoalWeightInternal(int32_t nodeIndex,
                                  float weight,
                                  float blendTime,
                                  float rate,
                                  qboolean create,
                                  uint16_t notifyName,
                                  uint16_t root)
{
    uint16_t handle =
        xanim_currentTree->poolNodeHandles[nodeIndex];
    XAnimInfo *node;

    if (handle == 0) {
        if (weight == 0.0f && create == qfalse) {
            return XANIM_SET_GOAL_WEIGHT_OK;
        }

        node = XAnimAllocInfo(xanim_currentTree, nodeIndex);
        Com_Memset(node->states, 0, sizeof(node->states));
        node->notifyName = 0;
        node->notifyIndex = -1;
        node->notifyChildIndex = 0;
        node->notifyType = 0;
    } else {
        node = &xanim_pool[handle];
        if (xanim_activePoolPayloadSlot != 0 && node->notifyName != 0) {
            SL_RemoveRefToString(node->notifyName);
        }
    }

    XAnimState *slot =
        &node->states[xanim_activePoolPayloadSlot];

    if (nodeIndex == XANIM_ROOT_NODE_INDEX) {
        weight = 1.0f;
        blendTime = 0.0f;
        rate = 1.0f;
    }

    slot->targetWeight = weight;
    /* Stock's call at 0x080c06e3 multiplies the raw st0 return of
     * XAnimFloatAbs at 0x080c1814; no float narrowing precedes the product. */
    slot->weightBlendTimeRemaining =
        XAnimFloatAbs(weight - slot->currentWeight) * blendTime;
    if (slot->weightBlendTimeRemaining < XANIM_WEIGHT_EPSILON) {
        slot->weightBlendTimeRemaining = 0.0f;
        slot->currentWeight = weight;
    } else if (slot->currentWeight == 0.0f) {
        slot->currentWeight = weight * XANIM_WEIGHT_EPSILON;
    }

    slot->rateScale = rate;

    if (xanim_activePoolPayloadSlot == 0) {
        node->notifyType = (uint16_t)root;
        return XANIM_SET_GOAL_WEIGHT_OK;
    }

    node->notifyName = notifyName;
    if (notifyName != 0) {
        SL_AddRefToString(notifyName);
    }
    node->notifyIndex = -1;

    XAnimEntry *entry =
        &xanim_currentTree->sourceTree->entries[nodeIndex];
    if (notifyName == 0 || entry->childCount == 0 ||
        (entry->payload.parent.flags & XANIM_SYNC_FLAG_MASK) == 0) {
        node->notifyChildIndex = 0;
        return XANIM_SET_GOAL_WEIGHT_OK;
    }

    node->notifyChildIndex =
        (uint16_t)XAnimGetDescendantWithGreatestWeight(
            nodeIndex);
    return node->notifyChildIndex == 0
               ? XANIM_SET_GOAL_WEIGHT_NOTIFY_SOURCE_MISSING
               : XANIM_SET_GOAL_WEIGHT_OK;
}

void XAnimEnsureGoalWeightParent(int32_t nodeIndex,
                                 float blendTime)
{
    int32_t parentIndex = nodeIndex;

    while (parentIndex != XANIM_ROOT_NODE_INDEX) {
        parentIndex =
            xanim_currentTree->sourceTree->entries[parentIndex].parentIndex;
        if (xanim_currentTree->poolNodeHandles[parentIndex] != 0) {
            return;
        }

        XAnimSetGoalWeightInternal(parentIndex, 0.0f, blendTime, 1.0f, qtrue,
                                   0, 0);
    }
}

int32_t XAnimSetGoalWeight(XAnimTree *runtimeTree,
                           int32_t animIndex, int32_t weightBits,
                           int32_t blendTimeBits, int32_t rateBits,
                           uint16_t notifyName, uint16_t root,
                           qboolean restart)
{
    float weight = coduo_engine_xanim_weight_from_bits(weightBits);
    float blendTime =
        coduo_engine_xanim_blend_time_from_bits(blendTimeBits);
    float rate = coduo_engine_xanim_rate_from_bits(rateBits);

    xanim_currentTree = runtimeTree;
    if (weight < XANIM_WEIGHT_EPSILON) {
        weight = 0.0f;
    }

    int32_t result = XAnimSetGoalWeightInternal(
        animIndex, weight, blendTime, rate, qfalse, notifyName, root);
    XAnimEnsureGoalWeightParent(animIndex, blendTime);
    XAnimUpdateSyncTime(animIndex, restart);
    if (xanim_activePoolPayloadSlot != 0) {
        XAnimUpdateServerNotify(animIndex);
    }

    return result;
}

void XAnimSetCompleteGoalWeight(XAnimTree *runtimeTree,
                                int32_t animIndex, int32_t weightBits,
                                int32_t blendTimeBits, int32_t rateBits,
                                uint16_t notifyName, uint16_t root,
                                qboolean restart)
{
    float weight = coduo_engine_xanim_weight_from_bits(weightBits);
    float blendTime =
        coduo_engine_xanim_blend_time_from_bits(blendTimeBits);
    float rate = coduo_engine_xanim_rate_from_bits(rateBits);

    xanim_currentTree = runtimeTree;
    if (weight < XANIM_WEIGHT_EPSILON) {
        weight = 0.0f;
    }

    XAnimSetGoalWeightInternal(animIndex, weight, blendTime, rate, qfalse,
                               notifyName, root);

    int32_t parentIndex = animIndex;
    while (parentIndex != XANIM_ROOT_NODE_INDEX) {
        parentIndex =
            runtimeTree->sourceTree->entries[parentIndex].parentIndex;
        uint16_t handle =
            runtimeTree->poolNodeHandles[parentIndex];

        if (handle == 0 ||
            xanim_pool[handle]
                    .states[xanim_activePoolPayloadSlot]
                    .targetWeight == 0.0f) {
            XAnimSetGoalWeightInternal(parentIndex, 1.0f, blendTime, 1.0f,
                                       qfalse, 0, 0);
        }
    }

    XAnimUpdateSyncTime(animIndex, restart);
    if (xanim_activePoolPayloadSlot != 0) {
        XAnimUpdateServerNotify(animIndex);
    }
}

void XAnimSetGoalWeightKnob(XAnimTree *runtimeTree,
                            int32_t animIndex, int32_t weightBits,
                            int32_t blendTimeBits, int32_t rateBits,
                            uint16_t notifyName, uint16_t root,
                            qboolean restart)
{
    float weight = coduo_engine_xanim_weight_from_bits(weightBits);
    float blendTime =
        coduo_engine_xanim_blend_time_from_bits(blendTimeBits);

    if (weight < XANIM_WEIGHT_EPSILON) {
        weight = 0.0f;
        weightBits = 0;
    }

    XAnimClearGoalWeightKnobInternal(runtimeTree, animIndex, weight, blendTime);
    XAnimSetGoalWeight(runtimeTree, animIndex, weightBits, blendTimeBits,
                       rateBits, notifyName, root, restart);
}

int32_t XAnimSetCompleteGoalWeightKnobAll(
    XAnimTree *runtimeTree, int32_t animIndex, int32_t knob,
    int32_t weightBits, int32_t blendTimeBits, int32_t rateBits,
    uint16_t notifyName, int32_t root, qboolean restart)
{
    float weight = coduo_engine_xanim_weight_from_bits(weightBits);
    float blendTime =
        coduo_engine_xanim_blend_time_from_bits(blendTimeBits);
    float rate = coduo_engine_xanim_rate_from_bits(rateBits);

    xanim_currentTree = runtimeTree;
    if (weight < XANIM_WEIGHT_EPSILON) {
        weight = 0.0f;
    }

    XAnimClearGoalWeightKnobInternal(runtimeTree, animIndex, weight, blendTime);
    int32_t result =
        XAnimSetGoalWeightInternal(animIndex, weight, blendTime, rate, qfalse,
                                   notifyName, (uint16_t)root);
    XAnimEnsureGoalWeightParent(animIndex, blendTime);
    XAnimUpdateSyncTime(animIndex, restart);
    if (xanim_activePoolPayloadSlot != 0) {
        XAnimUpdateServerNotify(animIndex);
    }

    int32_t parentIndex = animIndex;
    while (parentIndex != XANIM_ROOT_NODE_INDEX) {
        parentIndex =
            runtimeTree->sourceTree->entries[parentIndex].parentIndex;
        if (parentIndex == knob) {
            return result;
        }

        XAnimClearGoalWeightKnobInternal(runtimeTree, parentIndex, 1.0f,
                                         blendTime);
        XAnimSetGoalWeightInternal(parentIndex, 1.0f, blendTime, 1.0f,
                                   qfalse, 0, 0);
        XAnimUpdateSyncTime(parentIndex, restart);
        if (xanim_activePoolPayloadSlot != 0) {
            XAnimUpdateServerNotify(parentIndex);
        }
    }

    return XANIM_SET_GOAL_WEIGHT_TREE_COMPLETE;
}

void XAnimClearChildGoalWeights(XAnimTree *runtimeTree,
                                int32_t nodeIndex,
                                float blendTime)
{
    if (blendTime < XANIM_WEIGHT_EPSILON) {
        blendTime = 0.0f;
    }

    XAnimEntry *entry = &runtimeTree->sourceTree->entries[nodeIndex];
    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        XAnimClearGoalWeight(runtimeTree,
                             entry->payload.parent.firstChildIndex + child,
                             blendTime);
    }
}

void XAnimClearTreeWeights(XAnimTree *runtimeTree,
                           int32_t nodeIndex)
{
    uint16_t handle =
        runtimeTree->poolNodeHandles[nodeIndex];

    if (handle == 0) {
        return;
    }

    XAnimEntry *entry = &runtimeTree->sourceTree->entries[nodeIndex];
    for (int32_t child = 0; child < (int32_t)entry->childCount; ++child) {
        XAnimClearTreeWeights(runtimeTree,
                              entry->payload.parent.firstChildIndex + child);
    }

    XAnimFreeInfo(runtimeTree, handle);
    runtimeTree->poolNodeHandles[nodeIndex] = 0;
}

void XAnimClearTree(XAnimTree *runtimeTree)
{
    XAnimClearTreeWeights(runtimeTree, XANIM_ROOT_NODE_INDEX);

    size_t nodeCount = runtimeTree->sourceTree->nodeCount;
    for (int32_t selector = 0;
         selector < XANIM_PART_REMAP_TABLE_COUNT; ++selector) {
        uint8_t *partRemapTableBytes =
            coduo_engine_xanim_part_remap_table_bytes(runtimeTree, selector);

        for (int32_t nodeIndex = (int32_t)nodeCount - 1; nodeIndex >= 0;
             --nodeIndex) {
            uint16_t handle = coduo_engine_xanim_part_remap_handle_load(
                partRemapTableBytes, (size_t)nodeIndex);
            if (handle == 0) {
                continue;
            }

            XAnimEntry *entry =
                &runtimeTree->sourceTree->entries[nodeIndex];
            XAnimParts *loadedRecord =
                entry->payload.leafAsset->data.xanimParts;
            SL_RemoveRefToStringOfLen(
                handle, (int16_t)loadedRecord->partNameHandles[0] +
                            CODUO_XANIM_PART_REMAP_PREFIX_SIZE);
            coduo_engine_xanim_part_remap_handle_store(
                partRemapTableBytes, (size_t)nodeIndex, 0);
        }
    }
}

void XAnimLoadAnimState(XAnimState *slot)
{
    slot->time = (float)ReadFloat();
    slot->oldTime = (float)ReadFloat();
    slot->cycleCount = ReadShort();
    slot->oldCycleCount = ReadShort();
    slot->targetWeight = (float)ReadFloat();
    slot->rateScale = (float)ReadFloat();
    slot->weightBlendTimeRemaining = (float)ReadFloat();
    slot->currentWeight = (float)ReadFloat();
}

void XAnimSaveAnimState(const XAnimState *slot)
{
    WriteFloat(slot->time);
    WriteFloat(slot->oldTime);
    WriteShort(slot->cycleCount);
    WriteShort(slot->oldCycleCount);
    WriteFloat(slot->targetWeight);
    WriteFloat(slot->rateScale);
    WriteFloat(slot->weightBlendTimeRemaining);
    WriteFloat(slot->currentWeight);
}

void XAnimLoadAnimInfo(XAnimInfo *node)
{
    node->notifyIndex = (int16_t)ReadShort();
    node->notifyChildIndex =
        (uint16_t)ReadShort();
    node->notifyType =
        (uint16_t)ReadShort();

    if (ReadByte() == qfalse) {
        node->notifyName = 0;
    } else {
        node->notifyName = ReadString();
    }

    XAnimLoadAnimState(
        &node->states[XANIM_PRIMARY_POOL_PAYLOAD_SLOT]);
    XAnimLoadAnimState(
        &node->states[XANIM_SECONDARY_POOL_PAYLOAD_SLOT]);
}

void XAnimSaveAnimInfo(const XAnimInfo *node)
{
    WriteShort((uint16_t)node->notifyIndex);
    WriteShort(node->notifyChildIndex);
    WriteShort(node->notifyType);

    if (node->notifyName == 0) {
        WriteByte(qfalse);
    } else {
        WriteByte(qtrue);
        WriteString(node->notifyName);
    }

    XAnimSaveAnimState(
        &node->states[XANIM_PRIMARY_POOL_PAYLOAD_SLOT]);
    XAnimSaveAnimState(
        &node->states[XANIM_SECONDARY_POOL_PAYLOAD_SLOT]);
}

void XAnimCloneAnimInfo(const XAnimInfo *source,
                        XAnimInfo *dest)
{
    *dest = *source;

    if (dest->notifyName != 0) {
        SL_AddRefToString(dest->notifyName);
    }
}

void XAnimLoadAnimTree(XAnimTree *runtimeTree)
{
    /* Stock 0x080c1562..0x080c156f forms this count in a wrapping dword. */
    uint32_t nodeCount = runtimeTree->sourceTree->nodeCount;
    uint32_t bitsetByteCount = ((nodeCount - 1U) >> 3) + 1U;
    uint8_t liveNodeBits[bitsetByteCount];

    ScriptLoad_ReadData(liveNodeBits, bitsetByteCount);

    for (uint32_t nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) {
        if ((liveNodeBits[nodeIndex >> 3] & (1U << (nodeIndex & 7U))) == 0) {
            continue;
        }

        XAnimInfo *node =
            XAnimAllocInfo(runtimeTree, (int32_t)nodeIndex);
        XAnimLoadAnimInfo(node);
    }
}

void XAnimSaveAnimTree(XAnimTree *runtimeTree)
{
    /* Stock 0x080c1603..0x080c1610 forms this count in a wrapping dword. */
    uint32_t nodeCount = runtimeTree->sourceTree->nodeCount;
    uint32_t bitsetByteCount = ((nodeCount - 1U) >> 3) + 1U;
    uint8_t liveNodeBits[bitsetByteCount];

    Com_Memset(liveNodeBits, 0, (int)bitsetByteCount);
    for (uint32_t nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) {
        if (runtimeTree->poolNodeHandles[nodeIndex] != 0) {
            liveNodeBits[nodeIndex >> 3] =
                (uint8_t)(liveNodeBits[nodeIndex >> 3] |
                          (1U << (nodeIndex & 7U)));
        }
    }

    ScriptSave_WriteData(liveNodeBits, bitsetByteCount);

    for (uint32_t nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) {
        uint16_t handle =
            runtimeTree->poolNodeHandles[nodeIndex];
        if (handle != 0) {
            XAnimSaveAnimInfo(&xanim_pool[handle]);
        }
    }
}

void XAninCloneAnimTree(XAnimTree *sourceTree,
                        XAnimTree *destTree)
{
    int32_t nodeCount = (int32_t)sourceTree->sourceTree->nodeCount;

    for (int32_t nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) {
        uint16_t sourceHandle =
            sourceTree->poolNodeHandles[nodeIndex];

        if (sourceHandle == 0) {
            uint16_t destHandle =
                destTree->poolNodeHandles[nodeIndex];
            if (destHandle != 0) {
                XAnimFreeInfo(destTree, destHandle);
                destTree->poolNodeHandles[nodeIndex] = 0;
            }
            continue;
        }

        XAnimInfo *destNode;
        uint16_t destHandle =
            destTree->poolNodeHandles[nodeIndex];
        if (destHandle == 0) {
            destNode = XAnimAllocInfo(destTree, nodeIndex);
        } else {
            destNode = &xanim_pool[destHandle];
            XAnimClearServerInfo(destNode);
        }

        XAnimCloneAnimInfo(&xanim_pool[sourceHandle], destNode);
    }
}

long double XAnimFloatAbs(float value)
{
    return (long double)fabsf(value);
}

int32_t XAnimSignExtendShort(int16_t value)
{
    return value;
}

void XModelEnforceExist(qboolean enforce)
{
    xmodel_enforceExist = enforce;
}

void XAnimSetCompleteGoalWeightKnob(XAnimTree *runtimeTree,
                                    int32_t animIndex, int32_t weightBits,
                                    int32_t blendTimeBits, int32_t rateBits,
                                    uint16_t notifyName, uint16_t root,
                                    qboolean restart)
{
    float weight = coduo_engine_xanim_weight_from_bits(weightBits);
    float blendTime =
        coduo_engine_xanim_blend_time_from_bits(blendTimeBits);

    if (weight < XANIM_WEIGHT_EPSILON) {
        weight = 0.0f;
        weightBits = 0;
    }

    XAnimClearGoalWeightKnobInternal(runtimeTree, animIndex, weight, blendTime);
    XAnimSetCompleteGoalWeight(runtimeTree, animIndex, weightBits,
                               blendTimeBits, rateBits, notifyName, root,
                               restart);
}

void XAnimSetAnimRate(XAnimTree *runtimeTree,
                      int32_t animIndex, int32_t rateBits)
{
    xanim_currentTree = runtimeTree;
    XAnimSetAnimRateInternal(
        animIndex, coduo_engine_xanim_rate_from_bits(rateBits));
}

qboolean XAnimIsLooped(XAnim *tree, int32_t animIndex)
{
    XAnimEntry *entry = &tree->entries[animIndex];

    if (entry->childCount == 0) {
        return coduo_engine_xanim_script_entry_loaded_record(entry)->looped !=
                       0
                   ? qtrue
                   : qfalse;
    }

    return (entry->payload.parent.flags & XANIM_SYNC_FLAG_LOOPING) != 0
               ? qtrue
               : qfalse;
}

qboolean XAnimNotetrackExists(XAnim *tree, int32_t animIndex,
                              uint16_t notetrack)
{
    xanim_notetrack_t *noteTrack =
        coduo_engine_xanim_script_entry_loaded_record(
            &tree->entries[animIndex])
            ->noteTracks;

    if (noteTrack != NULL) {
        for (; noteTrack->nameHandle != 0; ++noteTrack) {
            if (noteTrack->nameHandle == notetrack) {
                return qtrue;
            }
        }
    }

    return qfalse;
}

void XAnimCopySecondaryTreeStateToPrimary(DObj *state)
{
    if (state->runtimeTree == NULL) {
        return;
    }

    xanim_currentTree = state->runtimeTree;
    XAnimUpdateOldServerTime(XANIM_ROOT_NODE_INDEX);
}

void XAnimCalcDelta(XAnimTree *runtimeTree,
                    int32_t animIndex, float *rotationDelta,
                    vec3_t moveDelta, int32_t weightSelector)
{
    float delta[XANIM_DELTA_COMPONENT_COUNT];

    xanim_evalLeafOutputMode = qfalse;
    xanim_evalPoolWeightSelector = weightSelector;
    XAnimCalcDeltaTree(runtimeTree, (uint32_t)animIndex,
                       XANIM_DELTA_EVAL_ROOT_WEIGHT, delta, qtrue, qfalse);
    coduo_engine_xanim_store_delta_output(
        delta, rotationDelta, moveDelta, qtrue);
}

void XAnimCalcAbsDelta(XAnimTree *runtimeTree,
                       int32_t animIndex, float *rotationDelta,
                       vec3_t moveDelta)
{
    float delta[XANIM_DELTA_COMPONENT_COUNT];

    xanim_evalLeafOutputMode = qtrue;
    xanim_evalPoolWeightSelector = 1;
    XAnimCalcDeltaTree(runtimeTree, (uint32_t)animIndex,
                       XANIM_DELTA_EVAL_ROOT_WEIGHT, delta, qtrue, qfalse);
    coduo_engine_xanim_store_delta_output(
        delta, rotationDelta, moveDelta, qfalse);
}

void XAnimGetRelDelta(XAnim *tree, int32_t animIndex,
                      float *rotationDelta, vec3_t moveDelta,
                      float startTime,
                      float endTime)
{
    float delta[XANIM_DELTA_COMPONENT_COUNT];
    coduo_engine_xanim_clear_delta(delta);

    XAnimEntry *entry = &tree->entries[animIndex];
    if (entry->childCount == 0) {
        XAnimParts *loadedRecord =
            entry->payload.leafAsset->data.xanimParts;

        if (loadedRecord->hasDeltaMotion != 0) {
            XAnimCalcRelDeltaParts(
                loadedRecord, XANIM_DELTA_EVAL_ROOT_WEIGHT, delta,
                startTime, endTime);
        }
    }

    coduo_engine_xanim_store_delta_output(
        delta, rotationDelta, moveDelta, qfalse);
}

void XAnimGetAbsDelta(XAnim *tree, int32_t animIndex,
                      float *rotationDelta, vec3_t moveDelta,
                      float time)
{
    float delta[XANIM_DELTA_COMPONENT_COUNT];
    coduo_engine_xanim_clear_delta(delta);

    XAnimEntry *entry = &tree->entries[animIndex];
    if (entry->childCount == 0) {
        XAnimParts *loadedRecord =
            entry->payload.leafAsset->data.xanimParts;

        if (loadedRecord->hasDeltaMotion != 0) {
            XAnimCalcAbsDeltaParts(
                loadedRecord, XANIM_DELTA_EVAL_ROOT_WEIGHT,
                delta, time);
        }
    }

    coduo_engine_xanim_store_delta_output(
        delta, rotationDelta, moveDelta, qfalse);
}
