/*
 * Source reconstruction for player animation lookup helpers.
 *
 * Binary SHA-256:
 * 4d2391c10e234559ace294fb69d96741cad522653c361f13f626f4dc1891acb7
 */

#include <ctype.h>
#include <math.h>
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "recovered_game.h"
#include "coduo_ctype_compat.h"
#include "game_globals.h"
#include "g_syscalls.h"
#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */
#include "coduo_native_x87.h"
#include "coduo_libm.h"
#include "q_string_compare.h"

#define ERROR_DROP 1
#define BG_ANIM_HASH_INITIAL_MULTIPLIER 119
#define BG_ANIM_PARSE_ERROR_BUFFER_SIZE 1024
#define BG_ANIM_TREE_NAME "multiplayer"
#define BG_ANIM_PARSE_FILE_ERROR COM_ERROR_MARKER "%s: (%s, line %i)"
#define BG_ANIM_PARSE_ERROR COM_ERROR_MARKER "%s"
#define BG_ANIM_INDEX_NOT_FOUND_ERROR \
    "BG_AnimationIndexForString: unknown player animation '%s'"
#define BG_ANIM_FOR_STRING_NOT_FOUND_ERROR \
    COM_ERROR_MARKER "BG_AnimationForString: unknown player animation '%s'"
#define BG_INDEX_FOR_STRING_NOT_FOUND_ERROR \
    "BG_IndexForString: unknown token '%s'"
#define BG_FIND_ANIM_TREE_ERROR COM_ERROR_MARKER "Could not find animation tree '%s'"
#define BG_COPY_STRING_OVERFLOW_ERROR \
    "BG_CopyStringIntoBuffer: out of buffer space"
#define BG_WEAPON_STRING_TABLE_ENTRIES 128
#define BG_WEAPON_STRING_TABLE_STOCK_I386_BYTES 1024
#define BG_WEAPON_STRING_NONE "none"
#define BG_ANIM_INDEX_RANGE_ERROR \
    COM_ERROR_MARKER "Player animation index %i out of 0 to %i range"
#define BG_UPDATE_ANIM_SLOT_RANGE_ERROR \
    COM_ERROR_MARKER "Player animation index out of range (%i): %i"
#define BG_ANIM_ROOT_NAME "root"
#define BG_ANIM_UNUSED_NAME "unused"
#define BG_ANIM_ROOT_FLAGS \
    (BG_ANIM_ENTRY_NON_PRIMITIVE | BG_ANIM_ENTRY_UNUSED)
#define BG_ANIM_BLEND_NONE (-1)
#define BG_ANIM_MIN_DURATION_MS 500
#define BG_ANIM_SCRIPT_FILE_MAX_BYTES 99998
#define BG_ANIM_SCRIPT_FILE_BUFFER_SIZE (BG_ANIM_SCRIPT_FILE_MAX_BYTES + 1)
#define BG_ANIM_CONDITION_ALIAS_ENTRIES 16
#define BG_ANIM_CONDITION_ALIAS_STRING_BUFFER_SIZE 10000
#define BG_PARSE_TOKEN_BUFFER_SIZE 64
#define BG_PARSE_TOKEN_NONE "none"
#define BG_PARSE_TOKEN_NONE_COMMA "none,"
#define BG_PARSE_TOKEN_COMMA ","
#define BG_PARSE_TOKEN_MINUS_SYMBOL "-"
#define BG_PARSE_TOKEN_MINUS "MINUS"
#define BG_PARSE_TOKEN_AND "AND"
#define BG_PARSE_TOKEN_SPACE " "
#define BG_PARSE_TOKEN_ALL "all"
#define BG_PARSE_TOKEN_DEFAULT "default"
#define BG_PARSE_TOKEN_SET "set"
#define BG_PARSE_TOKEN_STATE "state"
#define BG_PARSE_TOKEN_STATECHANGE "statechange"
#define BG_PARSE_TOKEN_OPEN_BRACE "{"
#define BG_PARSE_TOKEN_CLOSE_BRACE "}"
#define BG_PARSE_TOKEN_EQUALS "="
#define BG_PARSE_TOKEN_DURATION "duration"
#define BG_PARSE_TOKEN_TURRETANIM "turretanim"
#define BG_PARSE_TOKEN_BLENDTIME "blendtime"
#define BG_PARSE_TOKEN_SOUND "sound"
#define BG_PARSE_TOKEN_WAV ".wav"
#define BG_PARSE_CONDITION_END_ERROR \
    "BG_ParseConditionBits: unexpected end of condition"
#define BG_PARSE_CONDITION_TOKEN_ERROR \
    "BG_ParseConditionBits: unexpected '%s'"
#define BG_PARSE_CONDITION_VALUE_END_ERROR \
    "BG_ParseConditions: expected condition value, found end of line"
#define BG_PARSE_NO_CONDITIONS_ERROR "BG_ParseConditions: no conditions found"
#define BG_PARSE_MAX_ANIMS_ERROR \
    "BG_ParseCommands: exceeded maximum number of animations (%i)"
#define BG_PARSE_EXPECTED_ANIM_ERROR "BG_ParseCommands: expected animation"
#define BG_PARSE_EXPECTED_DURATION_ERROR \
    "BG_ParseCommands: expected duration value"
#define BG_PARSE_TURRET_BODY_ERROR \
    "BG_ParseCommands: Turret animations can only be played on the 'both' body part"
#define BG_PARSE_EXPECTED_BLENDTIME_ERROR \
    "BG_ParseCommands: expected blendtime value"
#define BG_PARSE_EXPECTED_SOUND_ERROR "BG_ParseCommands: expected sound"
#define BG_PARSE_WAV_SOUND_ERROR \
    "BG_ParseCommands: wav files not supported, only sound scripts"
#define BG_PARSE_UNKNOWN_PARAMETER_ERROR \
    "BG_ParseCommands: unknown parameter '%s'"
#define BG_PARSE_SCRIPT_LOAD_ERROR \
    COM_ERROR_MARKER "Couldn't load player animation script %s\n"
#define BG_PARSE_UNEXPECTED_EOF_ERROR \
    "BG_AnimParseAnimScript: unexpected end of file: %s"
#define BG_PARSE_UNEXPECTED_TOKEN_ERROR \
    "BG_AnimParseAnimScript: unexpected '%s'"
#define BG_PARSE_EXPECTED_CONDITION_TYPE_ERROR \
    "BG_AnimParseAnimScript: expected condition type string"
#define BG_PARSE_DEFINE_TYPE_ERROR \
    "BG_AnimParseAnimScript: can not make a define of type '%s'"
#define BG_PARSE_EXPECTED_CONDITION_DEFINE_ERROR \
    "BG_AnimParseAnimScript: expected condition define string"
#define BG_PARSE_EXPECTED_EQUALS_EOF_ERROR \
    "BG_AnimParseAnimScript: expected '=', found end of line"
#define BG_PARSE_EXPECTED_EQUALS_TOKEN_ERROR \
    "BG_AnimParseAnimScript: expected '=', found '%s'"
#define BG_PARSE_EXPECTED_STATE_ERROR \
    "BG_AnimParseAnimScript: expected 'state'"
#define BG_PARSE_EXPECTED_STATE_TYPE_ERROR \
    "BG_AnimParseAnimScript: expected state type"
#define BG_PARSE_EXPECTED_OPEN_BRACE_ERROR \
    "BG_AnimParseAnimScript: expected '{'"
#define BG_PARSE_INTERNAL_ERROR "BG_AnimParseAnimScript: internal error"
#define BG_PARSE_MAX_LIST_ITEMS_ERROR \
    "BG_AnimParseAnimScript: exceeded maximum items per script (%i)"
#define BG_PARSE_MAX_GLOBAL_ITEMS_ERROR \
    "BG_AnimParseAnimScript: exceeded maximum global items (%i)"
#define BG_PARSE_EXPECTED_STATECHANGE_ERROR \
    "BG_AnimParseAnimScript: expected 'statechange', got '%s'"
#define BG_PARSE_EXPECTED_STATECHANGE_TYPE_ERROR \
    "BG_AnimParseAnimScript: expected <state type>"
#define BG_ANIM_FIRE_WEAPON_BLEND_TIME_MS 30
#define BG_ANIM_TIMER_PAD_MS 50
#define BG_ANIM_RESTART_MIN_TIMER_MS 50
#define BG_ANIM_CLIMB_MOVE_TYPE_MASK \
    ((1u << ANIM_MT_CLIMBUP) | (1u << ANIM_MT_CLIMBDOWN))
#define BG_ANIM_CROUCH_STATE_MASK 0x000000c4u
#define BG_ANIM_PRONE_STATE_MASK 0x00000308u
#define BG_PS_FLAG_100 0x00000100u
#define BG_PS_FLAG_IN_VEHICLE 0x00100000u
#define BG_PS_FLAG_MOVE_CONDITION_MASK 0x00006000u
#define BG_PS_FLAG_FIRE_POSITION_CHECK_MASK \
    (BG_PS_FLAG_IN_VEHICLE | BG_PS_FLAG_MOVE_CONDITION_MASK)
#define BG_STANCE_FLAG_SUPPRESS_INPUT_CONDITION 0x00800000u
#define BG_PMOVE_INPUT_CONDITION_BIT 0x01
#define BG_VEHICLE_POSITION_1 1
#define BG_VEHICLE_POSITION_2 2
#define BG_VEHICLE_POSITION_MIN_DIRECT 3
#define BG_VEHICLE_POSITION_MAX_DIRECT 6
#define BG_ANIM_UPDATE_PLAYER_STATE_CONDITIONS_VEHICLE_TYPE_ERROR \
    COM_ERROR_MARKER "BG_AnimUpdatePlayerStateConditions: Vehicle type unknown: %i"
#define BG_ANIM_PLAYER_CONDITIONS_VEHICLE_TYPE_ERROR \
    COM_ERROR_MARKER "BG_AnimPlayerConditions: Vehicle type unknown: %i"
#define BG_ANIM_RESET_BLEND_TIME_MS 150
#define BG_ANIM_NO_ANIMATION_BLEND_TIME_MS 200
#define BG_ANIM_BLEND_MOVING_IN_MS 120
#define BG_ANIM_BLEND_MOVING_OUT_MS 250
#define BG_ANIM_BLEND_IDLE_MS 170
#define BG_ANIM_TRANSITION_PAD_MS 400
#define BG_ANIM_DEFAULT_CYCLE_MS 1000
#define BG_ANIM_LENGTH_PAD_MS 200
#define BG_ANIM_MILLISECONDS_TO_SECONDS 0.001f
#define BG_ANIM_RATE_MIN 0.1f
#define BG_ANIM_RATE_ZERO_EPSILON 0.01f
#define BG_ANIM_RATE_FAST_THRESHOLD 2.0f
#define BG_ANIM_RATE_CAP_SLOW_MOVE 3.0f
#define BG_ANIM_RATE_CAP_FAST_MOVE 2.0f
#define BG_ANIM_RATE_CAP_VERTICAL_MOVE 4.0f
#define BG_ANIM_RATE_SPEED_BASE 20
#define BG_ANIM_RATE_SPEED_RANGE 130.0f
#define BG_ANIM_FAST_MOVE_SPEED_MIN 151
#define BG_ANIM_ANGLE_STEP_SCALE 0.05f
#define BG_ANIM_ANGLE_MIN_STEP 0.5f

/* clientInfo_t is now defined in recovered_game.h */

typedef char bg_static_animation_size_check[
    (sizeof(bg_static_animation_t) == 0x5c) ? 1 : -1];
typedef char bg_static_animation_table_count_check[
    (offsetof(bg_static_animation_table_t, count) == 0xb800) ? 1 : -1];
GAME_I386_LAYOUT_ASSERT(bg_anim_table_anims_check,
                      offsetof(bg_static_animation_table_t, anims) == 0xa7ac8);
GAME_I386_LAYOUT_ASSERT(bg_anim_table_size_check,
                      sizeof(bg_static_animation_table_t) == 0xa7acc);
typedef char bg_anim_table_script_lists_check[
    (offsetof(bg_static_animation_table_t, scriptLists) == 0x20eac) ? 1 : -1];
typedef char bg_anim_table_canned_list_check[
    (offsetof(bg_static_animation_table_t, cannedAnimationLists) == 0x14924) ? 1 : -1];
GAME_I386_LAYOUT_ASSERT(bg_anim_table_script_pool_check,
                      offsetof(bg_static_animation_table_t, scriptPool) == 0x21ac4);
GAME_I386_LAYOUT_ASSERT(bg_anim_table_script_pool_count_check,
                      offsetof(bg_static_animation_table_t, scriptPoolCount) == 0xa7ac4);
typedef char bg_static_animation_hash_check[
    (offsetof(bg_static_animation_t, hash) == 0x4c) ? 1 : -1];
typedef char bg_static_animation_used_check[
    (offsetof(bg_static_animation_t, usedByScript) == 0x58) ? 1 : -1];
typedef char bg_runtime_animation_size_check[
    (sizeof(bg_runtime_animation_t) == 0x48) ? 1 : -1];
#if UINTPTR_MAX == 0xffffffffu
typedef char bg_anim_script_command_size_check[
    (sizeof(bg_anim_script_command_t) == 0x10) ? 1 : -1];
#endif
typedef char bg_anim_condition_size_check[
    (sizeof(bg_anim_condition_t) == 0x0c) ? 1 : -1];
typedef char bg_anim_slot_animation_word_check[
    (offsetof(bg_anim_slot_t, animationWord) == 0x10) ? 1 : -1];
typedef char bg_anim_slot_animation_offset_check[
    (offsetof(bg_anim_slot_t, animationOffset) == 0x14) ? 1 : -1];
typedef char bg_anim_slot_anim_rate_check[
    (offsetof(bg_anim_slot_t, animRate) == 0x28) ? 1 : -1];
typedef char bg_anim_slot_last_update_time_check[
    (offsetof(bg_anim_slot_t, lastUpdateTime) == 0x2c) ? 1 : -1];

#define BG_INDEXED(name) { name, -1 }
#define BG_INDEXED_END { NULL, -1 }

bg_static_animation_table_t *bgAnimStaticTable;
static int32_t bgAnimScriptLoaded;
static bg_runtime_animation_t *bgRuntimeAnimations;
static int32_t bgRuntimeAnimationCountValue;
static int32_t *bgRuntimeAnimationCount = &bgRuntimeAnimationCountValue;
static const char *bgPlayerAnimScriptPath = "mp/playeranim.script";
bg_indexed_string_t weaponStrings[BG_WEAPON_STRING_TABLE_ENTRIES];
bg_indexed_string_t animStateStr[] = {
    BG_INDEXED("RELAXED"), BG_INDEXED("QUERY"), BG_INDEXED("ALERT"),
    BG_INDEXED("COMBAT"), BG_INDEXED_END
};
static bg_indexed_string_t bgAnimGroupStrings[] = {
    BG_INDEXED("** UNUSED **"), BG_INDEXED("IDLE"), BG_INDEXED("IDLECR"),
    BG_INDEXED("IDLEPRONE"), BG_INDEXED("WALK"), BG_INDEXED("WALKBK"),
    BG_INDEXED("WALKCR"), BG_INDEXED("WALKCRBK"),
    BG_INDEXED("WALKPRONE"), BG_INDEXED("WALKPRONEBK"),
    BG_INDEXED("RUN"), BG_INDEXED("RUNBK"), BG_INDEXED("RUNCR"),
    BG_INDEXED("RUNCRBK"), BG_INDEXED("TURNRIGHT"),
    BG_INDEXED("TURNLEFT"), BG_INDEXED("CLIMBUP"),
    BG_INDEXED("CLIMBDOWN"), BG_INDEXED_END, { NULL, 0 }
};
static bg_indexed_string_t bgAnimEventStrings[] = {
    BG_INDEXED("PAIN"), BG_INDEXED("DEATH"), BG_INDEXED("FIREWEAPON"),
    BG_INDEXED("JUMP"), BG_INDEXED("JUMPBK"), BG_INDEXED("LAND"),
    BG_INDEXED("DROPWEAPON"), BG_INDEXED("RAISEWEAPON"),
    BG_INDEXED("CLIMBMOUNT"), BG_INDEXED("CLIMBDISMOUNT"),
    BG_INDEXED("RELOAD"), BG_INDEXED("CROUCH_TO_PRONE"),
    BG_INDEXED("PRONE_TO_CROUCH"), BG_INDEXED("MELEEATTACK"),
    BG_INDEXED("LMG_DEPLOY"), BG_INDEXED("LMG_BREAKDOWN"),
    BG_INDEXED_END, { NULL, 0 }, { NULL, 0 }, { NULL, 0 }
};
bg_indexed_string_t animBodyPartsStr[] = {
    BG_INDEXED("** UNUSED **"), BG_INDEXED("LEGS"), BG_INDEXED("TORSO"),
    BG_INDEXED("BOTH"), BG_INDEXED_END
};
static bg_indexed_string_t animMountedStr[] = {
    BG_INDEXED("** UNUSED **"), BG_INDEXED("MG42"),
    BG_INDEXED("vehDriver"), BG_INDEXED("vehGunner"),
    BG_INDEXED("vehPassenger1"), BG_INDEXED("vehPassenger2"),
    BG_INDEXED("vehPassenger3"), BG_INDEXED("vehPassenger4"),
    BG_INDEXED_END, { NULL, 0 }, { NULL, 0 }, { NULL, 0 }
};
static bg_indexed_string_t animVehicleMotionStr[] = {
    BG_INDEXED("** UNUSED **"), BG_INDEXED("IDLE"),
    BG_INDEXED("FORWARD"), BG_INDEXED("REVERSING"), BG_INDEXED_END,
    { NULL, 0 }, { NULL, 0 }, { NULL, 0 }
};
static bg_indexed_string_t animVehicleStr[] = {
    BG_INDEXED("** UNUSED **"), BG_INDEXED("TANK"), BG_INDEXED("JEEP"),
    BG_INDEXED("FLAK88"), BG_INDEXED_END, { NULL, 0 }, { NULL, 0 },
    { NULL, 0 }
};
bg_indexed_string_t animWeaponClassStr[] = {
    BG_INDEXED("RIFLE"), BG_INDEXED("MG"), BG_INDEXED("SMG"),
    BG_INDEXED("LMG"), BG_INDEXED("PISTOL"), BG_INDEXED("GRENADE"),
    BG_INDEXED("ROCKETLAUNCHER"), BG_INDEXED("TURRET"),
    BG_INDEXED("SPOTTER"), BG_INDEXED("NON-PLAYER"),
    BG_INDEXED("FLAMETHROWER"), BG_INDEXED_END
};
bg_indexed_string_t animWeaponPositionStr[] = {
    BG_INDEXED("HIP"), BG_INDEXED("ADS"), BG_INDEXED_END
};
bg_indexed_string_t animStrafeStateStr[] = {
    BG_INDEXED("NOT"), BG_INDEXED("LEFT"), BG_INDEXED("RIGHT"),
    BG_INDEXED_END
};
static bg_indexed_string_t bgAnimConditionTypeStrings[] = {
    BG_INDEXED("WEAPONS"), BG_INDEXED("WEAPONCLASS"),
    BG_INDEXED("MOUNTED"), BG_INDEXED("VEHICLE"),
    BG_INDEXED("VEHICLE_MOTION"), BG_INDEXED("MOVETYPE"),
    BG_INDEXED("UNDERHAND"), BG_INDEXED("CROUCHING"),
    BG_INDEXED("FIRING"), BG_INDEXED("WEAPON_POSITION"),
    BG_INDEXED("STRAFING"), BG_INDEXED_END
};
static bg_indexed_string_t bgAnimParseSectionStrings[] = {
    BG_INDEXED("defines"), BG_INDEXED("animations"),
    BG_INDEXED("canned_animations"), BG_INDEXED("statechanges"),
    BG_INDEXED("events"), BG_INDEXED_END, { NULL, 0 }, { NULL, 0 }
};
static bg_anim_condition_type_t bgAnimConditionTypes[] = {
    { ANIM_CONDMODE_BITMASK, weaponStrings },
    { ANIM_CONDMODE_BITMASK, animWeaponClassStr },
    { ANIM_CONDMODE_EQUAL, animMountedStr },
    { ANIM_CONDMODE_EQUAL, animVehicleStr },
    { ANIM_CONDMODE_EQUAL, animVehicleMotionStr },
    { ANIM_CONDMODE_BITMASK, bgAnimGroupStrings },
    { ANIM_CONDMODE_EQUAL, NULL },
    { ANIM_CONDMODE_EQUAL, NULL },
    { ANIM_CONDMODE_EQUAL, NULL },
    { ANIM_CONDMODE_EQUAL, animWeaponPositionStr },
    { ANIM_CONDMODE_EQUAL, animStrafeStateStr }
};
static bg_indexed_string_t
    bgAnimConditionAliases[ANIM_COND_COUNT]
                          [BG_ANIM_CONDITION_ALIAS_ENTRIES];
static uint32_t bgAnimConditionAliasBits[ANIM_COND_COUNT]
                                        [BG_ANIM_CONDITION_ALIAS_ENTRIES][2];
static int32_t bgAnimParseCurrentAnimGroup;
static bg_anim_event_t bgAnimParseCurrentEvent;
static char bgAnimScriptFileBuffer[BG_ANIM_SCRIPT_FILE_BUFFER_SIZE];
static char bgAnimConditionAliasStringBuffer[
    BG_ANIM_CONDITION_ALIAS_STRING_BUFFER_SIZE];
static int32_t bgAnimConditionAliasCounts[ANIM_COND_COUNT];
static int32_t bgAnimConditionAliasStringUsed;

void Com_Error(int code, const char *format, ...);
int Com_GetCurrentParseLine(void);
void Com_BeginParseSession(const char *name);
void Com_EndParseSession(void);
void Scr_FindAnim(const char *treeName, const char *animName, void *outAnim);
int BG_GetNumWeapons(void);
const weaponInfo_t *BG_GetInfoForWeapon(int weapon);
void Q_strncpyz(char *dest, const char *src, int destsize);
typedef struct script_anim_tree_ref_s {
    XAnim *tree;
} script_anim_tree_ref_t;

uint32_t Scr_GetAnimsIndex(XAnim *anims);
void BG_AnimParseAnimScript(bg_static_animation_table_t *table,
                            bg_runtime_animation_t *runtimeAnimations,
                            int32_t *runtimeAnimationCount);
char *Com_Parse(void *parse);
char *Com_ParseOnLine(void *parse);
void Com_UngetToken(void);
void Com_BitSet(uint32_t *bits, int bit);
int Com_BitCheck(const uint32_t *bits, int bit);
void Q_strcat(char *dest, int size, const char *src);
void BG_UpdateConditionValue(int clientNum, int condition, int value,
                                    int checkConversion);
float AngleSubtract(float a, float b);
float AngleMod(float angle);
float AngleNormalize180(float angle);
void AnglesSubtract(const float *a, const float *b, float *out);
float VectorDistance(const float *a, const float *b);
void Com_Printf(const char *format, ...);
void G_AnimScriptSound(int entityNum, const char *soundAliasName);

void BG_AnimParseError(const char *format, ...);

#if UINTPTR_MAX == 0xffffffffu
typedef char bg_anim_script_record_size_check[
    (sizeof(bg_anim_script_t) == 0x10c) ? 1 : -1];
typedef char bg_anim_script_command_count_check[
    (offsetof(bg_anim_script_t, commandCount) == 0x88) ? 1 : -1];
typedef char bg_anim_script_commands_check[
    (offsetof(bg_anim_script_t, commands) == 0x8c) ? 1 : -1];
typedef char bg_anim_script_pool_capacity_check[
    (sizeof(((bg_static_animation_table_t *)0)->scriptPool) /
         sizeof(((bg_static_animation_table_t *)0)->scriptPool[0]) ==
     BG_ANIM_MAX_GLOBAL_SCRIPTS) ? 1 : -1];
#endif
typedef char bg_player_anim_pm_type_check[
    (offsetof(clientInfo_t, pmType) == 0x04) ? 1 : -1];
/* torsoTimer and clientNum offset checks removed: the comprehensive
 * clientInfo_t in recovered_game.h uses different offsets
 * (torsoTimer at BG_CI_TORSO_TIMER). */
typedef char bg_anim_player_info_client_num_check[
    (offsetof(clientInfo_t, clientNum) == 0x08) ? 1 : -1];

typedef char bg_anim_script_list_size_check[
    (sizeof(bg_anim_script_list_t) == 0x204) ? 1 : -1];
typedef char bg_anim_state_size_check[
    (ANIM_MT_COUNT * sizeof(bg_anim_script_list_t) == 0x2448) ? 1 : -1];
typedef char bg_anim_statechange_to_state_size_check[
    (ANIM_STATE_COUNT * sizeof(bg_anim_script_list_t) == 0x810) ? 1 : -1];
typedef char bg_anim_animation_lists_size_check[
    (ANIM_STATE_COUNT * ANIM_MT_COUNT * sizeof(bg_anim_script_list_t) ==
     sizeof(((bg_static_animation_table_t *)0)->animationLists)) ? 1 : -1];
typedef char bg_anim_canned_animation_lists_size_check[
    (ANIM_STATE_COUNT * ANIM_MT_COUNT * sizeof(bg_anim_script_list_t) ==
     sizeof(((bg_static_animation_table_t *)0)->cannedAnimationLists)) ? 1 : -1];
typedef char bg_anim_statechange_lists_size_check[
    (ANIM_STATE_COUNT * ANIM_STATE_COUNT * sizeof(bg_anim_script_list_t) ==
     sizeof(((bg_static_animation_table_t *)0)->statechangeLists)) ? 1 : -1];
/* NOT_FROM_ORIGINAL_SOURCE: packed XAnim word helper extracted from repeated CONCAT22 patterns. */
static uint32_t game_compat_bg_make_xanim(uint16_t animTree, uint16_t animIndex)
{
    return ((uint32_t)animTree << SCR_ANIM_TREE_INDEX_SHIFT) | animIndex;
}

#if UINTPTR_MAX != UINT32_MAX
static bg_anim_sound_alias_fn bgAnimSoundAliasCallback;
static bg_anim_sound_event_fn bgAnimSoundEventCallback;
#endif

/* NOT_FROM_ORIGINAL_SOURCE: callback setter factored out of G_InitGame. */
void game_compat_bg_set_anim_sound_callbacks(bg_anim_sound_alias_fn soundAlias,
                              bg_anim_sound_event_fn soundEvent)
{
#if UINTPTR_MAX == UINT32_MAX
    bgs.soundAliasCallback = soundAlias;
    bgs.soundEventCallback = soundEvent;
#else
    bgAnimSoundAliasCallback = soundAlias;
    bgAnimSoundEventCallback = soundEvent;
#endif
}

/* NOT_FROM_ORIGINAL_SOURCE: host-safe wrapper for the original table callback. */
static const char *game_compat_bg_anim_sound_alias(const char *name)
{
#if UINTPTR_MAX == UINT32_MAX
    return bgs.soundAliasCallback(name);
#else
    return bgAnimSoundAliasCallback(name);
#endif
}

/* NOT_FROM_ORIGINAL_SOURCE: host-safe wrapper for the original table callback. */
static void game_compat_bg_anim_sound_event(int clientNum, const char *soundAliasName)
{
#if UINTPTR_MAX == UINT32_MAX
    bgs.soundEventCallback(clientNum, soundAliasName);
#else
    bgAnimSoundEventCallback(clientNum, soundAliasName);
#endif
}

/* NOT_FROM_ORIGINAL_SOURCE: host-safe offset-to-pointer animation slot accessor. */
static bg_static_animation_t *game_compat_bg_anim_slot_animation(const bg_anim_slot_t *slot)
{
    if (slot->animationOffset == 0) {
        return NULL;
    }

    return (bg_static_animation_t *)(void *)
        &((uint8_t *)bgAnimStaticTable)[slot->animationOffset];
}

/* NOT_FROM_ORIGINAL_SOURCE: host-safe pointer-to-offset animation slot setter. */
static void game_compat_bg_set_anim_slot_animation(bg_anim_slot_t *slot,
                                    bg_static_animation_t *animation)
{
    if (animation == NULL) {
        slot->animationOffset = 0;
        return;
    }

    slot->animationOffset =
        (uint32_t)((uint8_t *)animation - (uint8_t *)bgAnimStaticTable);
}

/* NOT_FROM_ORIGINAL_SOURCE: typed client-info accessor for animation conditions.
 * Original i386 uses bgAnimStaticTable + 0xa7af4 + clientNum * 0x4d0. Native
 * pointers enlarge preceding bgs fields on 64-bit, so the portable path names
 * the actual bgs.clientinfo array instead of reproducing that byte offset. */
static clientInfo_t *game_compat_bg_player_anim_client_info(bg_static_animation_table_t *table,
                                             int clientNum)
{
#if UINTPTR_MAX == UINT32_MAX
    return &((clientInfo_t *)(void *)
        &((uint8_t *)table)[0x0a7af4])[clientNum];
#else
    (void)table;
    return &bgs.clientinfo[clientNum];
#endif
}

/* NOT_FROM_ORIGINAL_SOURCE: typed animation script-list address helper. */
static bg_anim_script_list_t *game_compat_bg_anim_script_animation_list(
    bg_static_animation_table_t *table, int stateIndex, int animGroup)
{
    return &table->animationLists[(size_t)stateIndex * ANIM_MT_COUNT +
                                  (size_t)animGroup];
}

/* NOT_FROM_ORIGINAL_SOURCE: typed canned-animation script-list address helper. */
static bg_anim_script_list_t *game_compat_bg_anim_script_canned_animation_list(
    bg_static_animation_table_t *table, int stateIndex, int animGroup)
{
    return &table->cannedAnimationLists[(size_t)stateIndex * ANIM_MT_COUNT +
                                        (size_t)animGroup];
}

/* NOT_FROM_ORIGINAL_SOURCE: typed statechange script-list address helper. */
static bg_anim_script_list_t *game_compat_bg_anim_script_state_change_list(
    bg_static_animation_table_t *table, int fromState, int toState)
{
    return &table->statechangeLists[(size_t)toState * ANIM_STATE_COUNT +
                                    (size_t)fromState];
}

/* NOT_FROM_ORIGINAL_SOURCE: the original i386 expression is
 * table + 0x1fa84 + event * 0x204. Direct client/server accesses prove that the
 * source-level table partitions this logical domain into eventLists[10] and
 * scriptLists[6]. Branch at that boundary so native C never indexes past the
 * first array while preserving the original address for every valid event. */
static bg_anim_script_list_t *game_compat_bg_anim_script_event_list(
    bg_static_animation_table_t *table, bg_anim_event_t event)
{
    if (event < ANIM_EVENT_RELOAD) {
        return &table->eventLists[event];
    }

    return &table->scriptLists[event - ANIM_EVENT_RELOAD];
}

/* NOT_FROM_ORIGINAL_SOURCE: host-safe script offset dereference helper. */
static bg_anim_script_t *game_compat_bg_anim_script_list_get(const bg_anim_script_list_t *scriptList,
                                              int index)
{
    return (bg_anim_script_t *)(void *)
        &((uint8_t *)bgAnimStaticTable)[scriptList->scriptOffsets[index]];
}

/* NOT_FROM_ORIGINAL_SOURCE: host-safe script offset append helper. */
static void game_compat_bg_anim_script_list_append(bg_anim_script_list_t *scriptList,
                                    bg_anim_script_t *script)
{
    const ptrdiff_t offset = (uint8_t *)script - (uint8_t *)bgAnimStaticTable;

    if (offset < 0 || (uintmax_t)offset > UINT32_MAX) {
        BG_AnimParseError(BG_PARSE_INTERNAL_ERROR);
    }

    scriptList->scriptOffsets[scriptList->count] = (uint32_t)offset;
    ++scriptList->count;
}

/* NOT_FROM_ORIGINAL_SOURCE: random command selection helper extracted from event/state paths. */
static bg_anim_script_command_t *game_compat_bg_select_random_anim_script_command(
    bg_anim_script_t *script)
{
    return &script->commands[rand() % script->commandCount];
}

/* NOT_FROM_ORIGINAL_SOURCE: typed condition-word accessor. */
static uint32_t *game_compat_bg_player_anim_condition_words(clientInfo_t *clientInfo,
                                             int conditionType)
{
    return clientInfo->conditionWords[conditionType];
}

/* NOT_FROM_ORIGINAL_SOURCE: condition mode predicate helper. */
static qboolean game_compat_bg_anim_condition_uses_bitmask(int conditionType)
{
    return bgAnimConditionTypes[conditionType].mode ==
           ANIM_CONDMODE_BITMASK;
}

/* NOT_FROM_ORIGINAL_SOURCE: typed gclient client number accessor. */
static int game_compat_bg_player_anim_script_client_num(const playerState_t *player)
{
    return player->psClientNum;
}

/* NOT_FROM_ORIGINAL_SOURCE: thin wrapper around BG_UpdateConditionValue. */
static void game_compat_bg_update_anim_condition(int clientNum, int conditionType, int value)
{
    BG_UpdateConditionValue(clientNum, conditionType, value, 1);
}

/* NOT_FROM_ORIGINAL_SOURCE: player-state vehicle position condition helper extracted from 0x1c973. */
/* VERIFIED_DECOMPILER(0x1c973, 2c973_BG_AnimUpdatePlayerStateConditions.c, VERIFY-WAVE4-ANIMATION-C01-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against parent vehicle-position branch: pos 1->movement 2, pos 2->movement 3, direct pos 3..6->pos+1. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_AnimUpdatePlayerStateConditions (0x1c973); no standalone original body. */
static void game_compat_bg_update_vehicle_position_condition(playerState_t *ps)
{
    if (ps->vehiclePosition == BG_VEHICLE_POSITION_1) {
        game_compat_bg_update_anim_condition(ps->psClientNum, ANIM_COND_MOUNTED,
                               ANIM_MOUNT_VEHICLE_DRIVER);
    } else if (ps->vehiclePosition == BG_VEHICLE_POSITION_2) {
        game_compat_bg_update_anim_condition(ps->psClientNum, ANIM_COND_MOUNTED,
                               ANIM_MOUNT_VEHICLE_GUNNER);
    } else if (ps->vehiclePosition >= BG_VEHICLE_POSITION_MIN_DIRECT &&
               ps->vehiclePosition <= BG_VEHICLE_POSITION_MAX_DIRECT) {
        game_compat_bg_update_anim_condition(ps->psClientNum, ANIM_COND_MOUNTED,
                               ps->vehiclePosition + 1);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: player-state vehicle type condition helper extracted from 0x1c973. */
/* VERIFIED_DECOMPILER(0x1c973, 2c973_BG_AnimUpdatePlayerStateConditions.c, VERIFY-WAVE4-ANIMATION-C01-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against parent vehicle-type branch and error path: type 2->1, type 1->2, type 5->3. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_AnimUpdatePlayerStateConditions (0x1c973); no standalone original body. */
static void game_compat_bg_update_vehicle_type_condition(playerState_t *ps)
{
    if (ps->vehicleType == VEHICLE_TYPE_TANK) {
        game_compat_bg_update_anim_condition(ps->psClientNum, ANIM_COND_VEHICLE,
                               ANIM_VEHICLE_TANK);
    } else if (ps->vehicleType == VEHICLE_TYPE_4_WHEEL) {
        game_compat_bg_update_anim_condition(ps->psClientNum, ANIM_COND_VEHICLE,
                               ANIM_VEHICLE_JEEP);
    } else if (ps->vehicleType == VEHICLE_TYPE_ARTILLERY) {
        game_compat_bg_update_anim_condition(ps->psClientNum, ANIM_COND_VEHICLE,
                               ANIM_VEHICLE_FLAK88);
    } else {
        Com_Error(ERROR_DROP,
                  BG_ANIM_UPDATE_PLAYER_STATE_CONDITIONS_VEHICLE_TYPE_ERROR,
                  ps->vehicleType);
    }
}

int BG_IndexForString(const char *name, bg_indexed_string_t *strings,
                      qboolean allowMissing);
int BG_AnimationIndexForString(const char *name);

/* NOT_FROM_ORIGINAL_SOURCE: parser null/empty token predicate helper. */
static qboolean game_compat_bg_string_is_null_or_empty(const char *text)
{
    return text == NULL || text[0] == '\0';
}

/* NOT_FROM_ORIGINAL_SOURCE: parser token comma trimming helper. */
static void game_compat_bg_trim_trailing_comma(char *text)
{
    const size_t length = strlen(text);

    if (length > 0 && text[length - 1] == ',') {
        text[length - 1] = '\0';
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: parser condition token concatenation helper. */
static void game_compat_bg_append_condition_token(char *condition, const char *token)
{
    if (condition[0] != '\0') {
        Q_strcat(condition, BG_PARSE_TOKEN_BUFFER_SIZE, BG_PARSE_TOKEN_SPACE);
    }

    Q_strcat(condition, BG_PARSE_TOKEN_BUFFER_SIZE, token);
}

/* NOT_FROM_ORIGINAL_SOURCE: parser condition terminator helper. */
static qboolean game_compat_bg_token_ends_condition(char *token, int *endOfLine)
{
    if (Q_stricmp(token, BG_PARSE_TOKEN_COMMA) == 0) {
        *endOfLine = 1;
        return 1;
    }

    if (Q_stricmp(token, BG_PARSE_TOKEN_NONE_COMMA) == 0) {
        *endOfLine = 1;
        return 1;
    }

    const size_t length = strlen(token);

    if (length > 0 && token[length - 1] == ',') {
        *endOfLine = 1;
        token[length - 1] = '\0';
        return 1;
    }

    return 0;
}

/* NOT_FROM_ORIGINAL_SOURCE: parser bit accumulator helper extracted from 0x1a41e. */
/* VERIFIED_DECOMPILER(0x1a41e, 2a41e_FUN_0002a41e.c, VERIFY-WAVE4-ANIMATION-C01-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against parent condition-token apply block: all bits, alias lookup, value bit set, subtract clear, and union OR. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_ParseConditionBits (0x1a41e); no standalone original body. */
static void game_compat_bg_apply_condition_bits(const char *condition, bg_indexed_string_t *values,
                                  int conditionIndex, qboolean subtract,
                                  uint32_t *currentBits, uint32_t *outBits)
{
    if (Q_stricmp(condition, BG_PARSE_TOKEN_ALL) == 0) {
        currentBits[0] = 0xffffffffu;
        currentBits[1] = 0xffffffffu;
    } else {
        int index = BG_IndexForString(condition, bgAnimConditionAliases[conditionIndex], 1);

        if (index < 0) {
            index = BG_IndexForString(condition, values, 0);
            Com_BitSet(currentBits, index);
        } else {
            currentBits[0] = bgAnimConditionAliasBits[conditionIndex][index][0];
            currentBits[1] = bgAnimConditionAliasBits[conditionIndex][index][1];
        }
    }

    if (subtract) {
        outBits[0] &= ~currentBits[0];
        outBits[1] &= ~currentBits[1];
    } else {
        outBits[0] |= currentBits[0];
        outBits[1] |= currentBits[1];
    }
}

/* 0x19a2c BG_StringHashValue */
/* VERIFIED_DECOMPILER(0x19a2c, 29a2c_FUN_00029a2c.c, VERIFY-WAVE4-ANIMATION-C01-2026-06-17): DATAFLOW_VERIFIED - tolower loop, signed-char multiply by index+0x77, -1 remap, and return checked against current decompiler output. */
static int BG_StringHashValue(const char *text)
{
    int hash = 0;

    for (int index = 0; text[index] != '\0'; ++index) {
        const int lower =
            tolower(coduo_ctype_signed_byte_arg(text[index]));

        hash += (int)(signed char)lower * (index + BG_ANIM_HASH_INITIAL_MULTIPLIER);
    }

    if (hash == -1) {
        hash = 0;
    }

    return hash;
}

/* 0x19a9c BG_AnimParseError */
/* VERIFIED_DECOMPILER(0x19a9c, 29a9c_BG_AnimParseError.c, VERIFY-WAVE4-ANIMATION-C01-2026-06-17): DATAFLOW_VERIFIED - 1024-byte message buffer, varargs vsprintf, no-script error path, script path parse-line +1 error path checked. */
void BG_AnimParseError(const char *format, ...)
{
    char message[BG_ANIM_PARSE_ERROR_BUFFER_SIZE];
    va_list args;

    /* ORIGINAL_BINARY_BUG: 0x19ab1..0x19ace passes no destination bound to
     * vsprintf.  Preserve the stock overflow behavior; see
     * original-bugs/game-bg-anim-parse-error-vsprintf-overflow.md. */
    va_start(args, format);
    vsprintf(message, format, args);
    va_end(args);

    if (bgPlayerAnimScriptPath == NULL) {
        Com_Error(ERROR_DROP, BG_ANIM_PARSE_ERROR, message);
    } else {
        const int line = Com_GetCurrentParseLine();

        Com_Error(ERROR_DROP, BG_ANIM_PARSE_FILE_ERROR, message,
                  bgPlayerAnimScriptPath, line + 1);
    }
}

/* 0x19b3b BG_AnimationIndexForString */
/* VERIFIED_DECOMPILER(0x19b3b, 29b3b_BG_AnimationIndexForString.c, VERIFY-WAVE4-ANIMATION-C01-2026-06-17): DATAFLOW_VERIFIED - static-table hash/name lookup, missing parse error, runtime lookup, Scr_FindAnim append, name/hash stores, count increment, and return index checked. */
int BG_AnimationIndexForString(const char *name)
{
    const int hash = BG_StringHashValue(name);

    if (bgRuntimeAnimations == NULL) {
        for (int index = 0; index < bgAnimStaticTable->count; ++index) {
            bg_static_animation_t *animation = &bgAnimStaticTable->entries[index];

            if (hash == animation->hash && Q_stricmp(name, animation->name) == 0) {
                return index;
            }
        }

        BG_AnimParseError(BG_ANIM_INDEX_NOT_FOUND_ERROR, name);
        return -1;
    }

    for (int index = 0; index < *bgRuntimeAnimationCount; ++index) {
        bg_runtime_animation_t *animation = &bgRuntimeAnimations[index];

        if (hash == animation->hash && Q_stricmp(name, animation->name) == 0) {
            return index;
        }
    }

    /* ORIGINAL_BINARY_BUG: 0x19c47..0x19cab neither bounds the 512-entry
     * producer array nor the 64-byte inline name before strcpy.  Preserve it;
     * see original-bugs/game-runtime-animation-registration-overflows.md. */
    bg_runtime_animation_t *animation = &bgRuntimeAnimations[*bgRuntimeAnimationCount];

    Scr_FindAnim(BG_ANIM_TREE_NAME, name, animation);
    strcpy(animation->name, name);
    animation->hash = hash;
    ++*bgRuntimeAnimationCount;

    return *bgRuntimeAnimationCount - 1;
}

/* 0x19ccb BG_AnimationForString */
/* VERIFIED_DECOMPILER(0x19ccb, 29ccb_BG_AnimationForString.c, VERIFY-WAVE4-ANIMATION-C01-2026-06-17): DATAFLOW_VERIFIED - static-table count loop, hash/name match, Com_Error missing path, and NULL fallback return checked. */
bg_static_animation_t *BG_AnimationForString(const char *name)
{
    const int hash = BG_StringHashValue(name);

    for (int index = 0; index < bgAnimStaticTable->count; ++index) {
        bg_static_animation_t *animation = &bgAnimStaticTable->entries[index];

        if (hash == animation->hash && Q_stricmp(name, animation->name) == 0) {
            return animation;
        }
    }

    Com_Error(ERROR_DROP, BG_ANIM_FOR_STRING_NOT_FOUND_ERROR, name);
    return NULL;
}

/* 0x19d71 BG_IndexForString */
/* VERIFIED_DECOMPILER(0x19d71, 29d71_BG_IndexForString.c, VERIFY-WAVE4-ANIMATION-C01-2026-06-17): DATAFLOW_VERIFIED - lazy string hash fill, hash/name match, sentinel termination, allow-missing branch, parse error, and -1 return checked. */
int BG_IndexForString(const char *name, bg_indexed_string_t *strings,
                      qboolean allowMissing)
{
    const int hash = BG_StringHashValue(name);

    for (int index = 0; strings[index].name != NULL; ++index) {
        if (strings[index].hash == -1) {
            strings[index].hash = BG_StringHashValue(strings[index].name);
        }

        if (hash == strings[index].hash &&
            Q_stricmp(name, strings[index].name) == 0) {
            return index;
        }
    }

    if (!allowMissing) {
        BG_AnimParseError(BG_INDEX_FOR_STRING_NOT_FOUND_ERROR, name);
    }

    return -1;
}

/* 0x19e29 BG_CopyStringIntoBuffer */
/* VERIFIED_DECOMPILER(0x19e29, 29e29_BG_CopyStringIntoBuffer.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - strlen overflow check, parse error, destination pointer, strcpy, used-byte increment, and return pointer checked. */
char *BG_CopyStringIntoBuffer(const char *text, char *buffer, uint32_t bufferSize,
                              int *used)
{
    if ((size_t)bufferSize <= strlen(text) + (size_t)*used + 1u) {
        BG_AnimParseError(BG_COPY_STRING_OVERFLOW_ERROR);
    }

    char *out = &buffer[*used];

    strcpy(out, text);
    *used += (int)strlen(text) + 1;

    return out;
}

/* 0x19e9f BG_InitWeaponStrings */
/* VERIFIED_DECOMPILER(0x19e9f, 29e9f_BG_InitWeaponStrings.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - 0x400-byte table clear, none entry hash, 1..BG_GetNumWeapons loop, pickup-name stores, and hash stores checked. */
void BG_InitWeaponStrings(void)
{
    (void)BG_WEAPON_STRING_TABLE_STOCK_I386_BYTES;

    memset(weaponStrings, 0,
           BG_WEAPON_STRING_TABLE_ENTRIES * sizeof(weaponStrings[0]));

    weaponStrings[0].name = BG_WEAPON_STRING_NONE;
    weaponStrings[0].hash = BG_StringHashValue(weaponStrings[0].name);

    for (int weapon = 1; weapon <= BG_GetNumWeapons(); ++weapon) {
        const weaponInfo_t *weaponInfo =
            (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);
        const char *pickupName = weaponInfo->pickupName;

        weaponStrings[weapon].name = pickupName;
        weaponStrings[weapon].hash = BG_StringHashValue(pickupName);
    }
}

/* 0x19f5b BG_LoadAnimForAnimIndex */
/* VERIFIED_DECOMPILER(0x19f5b, 29f5b_FUN_00029f5b.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - static table count range error, runtime count loop, 0x48-byte runtime stride, animIndex match, and NULL fallback checked. */
static bg_runtime_animation_t *BG_LoadAnimForAnimIndex(uint32_t animationIndex)
{
    if (animationIndex >= (uint32_t)bgAnimStaticTable->count) {
        Com_Error(ERROR_DROP, BG_ANIM_INDEX_RANGE_ERROR, animationIndex,
                  bgAnimStaticTable->count);
    }

    bg_runtime_animation_t *animation = bgRuntimeAnimations;

    for (int index = 0; index < *bgRuntimeAnimationCount; ++index, ++animation) {
        if (animationIndex == animation->anim.animIndex) {
            return animation;
        }
    }

    return NULL;
}

/* 0x19ffa BG_SetupAnimNoteTypes */
/* VERIFIED_DECOMPILER(0x19ffa, 29ffa_FUN_00029ffa.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - usedByScript clear over table count, script-list count scan, command body-part guards, and both animIndex usedByScript stores checked. */
static void BG_SetupAnimNoteTypes(bg_static_animation_table_t *table)
{
    for (int index = 0; index < table->count; ++index) {
        table->entries[index].usedByScript = 0;
    }

    bg_anim_script_list_t *scriptList = &table->scriptLists[0];

    for (int scriptIndex = 0; scriptIndex < scriptList->count; ++scriptIndex) {
        bg_anim_script_t *script = game_compat_bg_anim_script_list_get(scriptList, scriptIndex);

        for (int commandIndex = 0; commandIndex < script->commandCount; ++commandIndex) {
            bg_anim_script_command_t *command = &script->commands[commandIndex];

            if (command->bodyPart[0] != 0) {
                table->entries[command->animIndex[0]].usedByScript = 1;
            }

            if (command->bodyPart[1] != 0) {
                table->entries[command->animIndex[1]].usedByScript = 1;
            }
        }
    }
}

/* 0x1a0de BG_FinalizePlayerAnims */
/* VERIFIED_DECOMPILER(0x1a0de, 2a0de_BG_FinalizePlayerAnims.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - anim-tree count, root entry, runtime lookup, primitive/nonprimitive paths, blend defaults, duration/moveSpeed math, min duration, loop flag, script parse, and used-script mark checked. */
void BG_FinalizePlayerAnims(void)
{
    bg_static_animation_table_t *table = bgAnimStaticTable;
    XAnim *anims = table->anims;
    const uint16_t animTree = Scr_GetAnimsIndex(anims);
    const int animCount = trap_XAnimGetAnimTreeSize(anims);

    table->count = animCount;

    bg_static_animation_t *entry = &table->entries[0];

    entry->flags |= BG_ANIM_ROOT_FLAGS;
    Q_strncpyz(entry->name, BG_ANIM_ROOT_NAME, sizeof(entry->name));
    entry->hash = 0;

    for (int index = 1; index < animCount; ++index) {
        ++entry;

        const uint16_t animIndex = (uint16_t)index;
        const uint32_t anim = game_compat_bg_make_xanim(animTree, animIndex);
        bg_runtime_animation_t *runtime = BG_LoadAnimForAnimIndex(index);

        if (runtime == NULL) {
            entry->flags |= BG_ANIM_ENTRY_UNUSED;
            Q_strncpyz(entry->name, BG_ANIM_UNUSED_NAME, sizeof(entry->name));
            entry->hash = 0;
            continue;
        }

        if (!trap_XAnimIsPrimitive(anim)) {
            entry->flags |= BG_ANIM_ENTRY_NON_PRIMITIVE;
            Q_strncpyz(entry->name, runtime->name, sizeof(entry->name));
            entry->hash = runtime->hash;

            if (entry->blendTime == 0) {
                entry->blendTime = BG_ANIM_BLEND_NONE;
            }

            entry->duration = 0;
            entry->moveSpeed = 0;
            continue;
        }

        Q_strncpyz(entry->name, trap_XAnimGetAnimName(anim), sizeof(entry->name));
        entry->hash = BG_StringHashValue(entry->name);

        if (entry->blendTime == 0) {
            entry->blendTime = BG_ANIM_BLEND_NONE;
        }

        entry->duration = trap_XAnimGetLength(anims, animIndex);
        if (entry->duration == 0) {
            entry->moveSpeed = 0;
        } else {
            vec3_t rotationDelta;
            vec3_t moveDelta;

            trap_XAnimGetRelDelta(anim, rotationDelta, moveDelta, 0.0f, 1.0f);

            /* Stock 0x1a32c..0x1a34a: the 3-mul/2-add dot stays 80-bit, rounds
             * to double at the sqrt call boundary, result rounds to float -> shim. */
#if EMULATE_X87
            const float distance = (float)CoduoLibm_Sqrt(x87f_store_f64(
                x87f_add(x87f_add(
                    x87f_mul(x87f_load_f32(moveDelta[0]), x87f_load_f32(moveDelta[0])),
                    x87f_mul(x87f_load_f32(moveDelta[1]), x87f_load_f32(moveDelta[1]))),
                    x87f_mul(x87f_load_f32(moveDelta[2]), x87f_load_f32(moveDelta[2])))));
#else
            const float distance = (float)CoduoLibm_Sqrt((double)(moveDelta[0] * moveDelta[0] +
                                                        moveDelta[1] * moveDelta[1] +
                                                        moveDelta[2] * moveDelta[2]));
#endif
#if EMULATE_X87
            const int truncatedDistance =
                x87f_store_i32_trunc(x87f_load_f32(distance));
#elif defined(__i386__) || defined(__x86_64__)
            int truncatedDistance = CODUO_X87_TRUNCATE_I32(
                (long double)distance);
#else
            const int truncatedDistance =
                game_compat_int32_from_float_trunc(distance);
#endif

            if (truncatedDistance == 0) {
                entry->moveSpeed = 0;
            } else {
                entry->moveSpeed = (truncatedDistance * 1000) / entry->duration;
            }
        }

        if (entry->duration < BG_ANIM_MIN_DURATION_MS) {
            entry->duration = BG_ANIM_MIN_DURATION_MS;
        }

        if (trap_XAnimIsLooped(anim)) {
            entry->flags |= BG_ANIM_ENTRY_LOOPED;
        }
    }

    BG_AnimParseAnimScript(table, 0, 0);
    BG_SetupAnimNoteTypes(table);
}

/* 0x1a41e FUN_0002a41e */
/* VERIFIED_DECOMPILER(0x1a41e, 2a41e_FUN_0002a41e.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - parse/unget EOF handling, none/none-comma/comma paths, AND/MINUS handling, token append/trailing comma trim, apply/subtract bit math, and parse errors checked. */
static void BG_ParseConditionBits(char **parse, bg_indexed_string_t *values,
                           int conditionIndex, uint32_t *outBits)
{
    int endOfLine = 0;
    qboolean subtract = 0;
    char condition[BG_PARSE_TOKEN_BUFFER_SIZE];
    uint32_t currentBits[2] = {0, 0};

    condition[0] = '\0';
    outBits[0] = 0;

    while (!endOfLine) {
        char *token = Com_ParseOnLine(parse);

        if (game_compat_bg_string_is_null_or_empty(token)) {
            Com_UngetToken();
            endOfLine = 1;
            if (condition[0] == '\0') {
                return;
            }
        } else if (Q_stricmp(token, BG_PARSE_TOKEN_NONE) == 0) {
            Com_BitSet(outBits, 0);
            continue;
        } else if (Q_stricmp(token, BG_PARSE_TOKEN_NONE_COMMA) == 0) {
            Com_BitSet(outBits, 0);
            endOfLine = 1;
            continue;
        } else {
            if (Q_stricmp(token, BG_PARSE_TOKEN_MINUS_SYMBOL) == 0) {
                token = BG_PARSE_TOKEN_MINUS;
            }

            if (Q_stricmp(token, BG_PARSE_TOKEN_AND) != 0 &&
                Q_stricmp(token, BG_PARSE_TOKEN_MINUS) != 0) {
                game_compat_bg_token_ends_condition(token, &endOfLine);
                if (Q_stricmp(token, BG_PARSE_TOKEN_COMMA) != 0) {
                    game_compat_bg_append_condition_token(condition, token);
                }
                if (!endOfLine) {
                    continue;
                }
            }
        }

        if (condition[0] != '\0') {
            /* VERIFIED_DECOMPILER(0x1a41e, 2a41e_FUN_0002a41e.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED -
             * original keeps the temporary bit words live across AND/MINUS
             * chunks and only overwrites them for "all" or alias hits.
 */
            game_compat_bg_apply_condition_bits(condition, values, conditionIndex, subtract,
                                  currentBits, outBits);
            condition[0] = '\0';
            if (Q_stricmp(token, BG_PARSE_TOKEN_MINUS) == 0) {
                subtract = 1;
            }
            continue;
        }

        if (endOfLine) {
            BG_AnimParseError(BG_PARSE_CONDITION_END_ERROR);
        } else if (Q_stricmp(token, BG_PARSE_TOKEN_MINUS) == 0) {
            subtract = 1;
        } else {
            BG_AnimParseError(BG_PARSE_CONDITION_TOKEN_ERROR, token);
        }
    }
}

/* 0x1a7d0 FUN_0002a7d0 */
/* VERIFIED_DECOMPILER(0x1a7d0, 2a7d0_FUN_0002a7d0.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - line-token loop, default return, condition type lookup, bitset/value modes, trailing comma trim, condition record stores, count increment, no-condition error, and return checked. */
static qboolean BG_ParseConditions(char **parse, bg_anim_script_t *script)
{
    int value0 = 0;
    int value1 = 0;
    char *token;

    while ((token = Com_ParseOnLine(parse)) != NULL && token[0] != '\0') {
        if (Q_stricmp(token, BG_PARSE_TOKEN_DEFAULT) == 0) {
            return 1;
        }

        const int conditionIndex = BG_IndexForString(token, bgAnimConditionTypeStrings, 0);
        bg_anim_condition_type_t *conditionType = &bgAnimConditionTypes[conditionIndex];

        if (conditionType->mode == ANIM_CONDMODE_BITMASK) {
            uint32_t bits[2] = {(uint32_t)value0, (uint32_t)value1};

            BG_ParseConditionBits(parse, conditionType->values, conditionIndex, bits);
            value0 = (int)bits[0];
            value1 = (int)bits[1];
        } else if (conditionType->mode == ANIM_CONDMODE_EQUAL) {
            if (conditionType->values == NULL) {
                value0 = 1;
            } else {
                token = Com_ParseOnLine(parse);
                if (game_compat_bg_string_is_null_or_empty(token)) {
                    BG_AnimParseError(BG_PARSE_CONDITION_VALUE_END_ERROR);
                }

                game_compat_bg_trim_trailing_comma(token);
                value0 = BG_IndexForString(token, conditionType->values, 0);
            }
        }

        /* ORIGINAL_BINARY_BUG: 0x1a927..0x1a97b never bounds the 11-entry
         * condition array before its three dword stores and count increment.
         * Preserve it; see
         * original-bugs/game-animation-script-condition-overflow.md. */
        bg_anim_condition_t *condition = &script->conditions[script->conditionCount];

        condition->type = conditionIndex;
        condition->value[0] = value0;
        condition->value[1] = value1;
        ++script->conditionCount;
    }

    if (script->conditionCount == 0) {
        BG_AnimParseError(BG_PARSE_NO_CONDITIONS_ERROR);
    }

    return 1;
}

/* NOT_FROM_ORIGINAL_SOURCE: sound-parameter tail helper extracted from 0x1a9a3. */
/* VERIFIED_DECOMPILER(0x1a9a3, 2a9a3_BG_ParseCommands.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - sound token loop, expected-sound error, .wav rejection, callback result store, and unknown-parameter error checked against original tail block. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_ParseCommands (0x1a9a3); no standalone original body. */
static void game_compat_bg_parse_command_parameters(char **parse,
                                      bg_anim_script_command_t *command)
{
    char *token;

    while ((token = Com_ParseOnLine(parse)) != NULL && token[0] != '\0') {
        if (Q_stricmp(token, BG_PARSE_TOKEN_SOUND) != 0) {
            BG_AnimParseError(BG_PARSE_UNKNOWN_PARAMETER_ERROR, token);
            continue;
        }

        token = Com_ParseOnLine(parse);
        if (game_compat_bg_string_is_null_or_empty(token)) {
            BG_AnimParseError(BG_PARSE_EXPECTED_SOUND_ERROR);
        }

        if (strstr(token, BG_PARSE_TOKEN_WAV) != NULL) {
            BG_AnimParseError(BG_PARSE_WAV_SOUND_ERROR);
        }

        command->soundAliasName = game_compat_bg_anim_sound_alias(token);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: static animation flag writes extracted from 0x1a9a3. */
/* VERIFIED_DECOMPILER(0x1a9a3, 2a9a3_BG_ParseCommands.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - static-only guard, stateFlags move-type bit, climb vertical-motion flag, STRAFING left/right flags, FIREWEAPON blend stores, and DEATH stores checked against original middle block. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_ParseCommands (0x1a9a3); no standalone original body. */
static void game_compat_bg_apply_static_command_flags(bg_anim_script_t *script,
                                       bg_anim_script_command_t *command,
                                       int bodySlot,
                                       bg_static_animation_table_t *table)
{
    if (bgRuntimeAnimations != NULL) {
        return;
    }

    bg_static_animation_t *animation = &table->entries[command->animIndex[bodySlot]];

    if (bgAnimParseCurrentAnimGroup != ANIM_MT_UNUSED &&
        command->bodyPart[bodySlot] != ANIM_BP_TORSO) {
        animation->stateFlags |= 1u << (bgAnimParseCurrentAnimGroup & 0x1f);

        if ((bgAnimParseCurrentAnimGroup == ANIM_MT_CLIMBUP ||
             bgAnimParseCurrentAnimGroup == ANIM_MT_CLIMBDOWN) &&
            animation->moveSpeed != 0) {
            animation->flags |= BG_ANIM_ENTRY_VERTICAL_MOTION;
        }

        for (int index = 0; index < script->conditionCount; ++index) {
            bg_anim_condition_t *condition = &script->conditions[index];

            if (condition->type == ANIM_COND_STRAFING) {
                if (condition->value[0] == ANIM_STRAFE_LEFT) {
                    animation->flags |= BG_ANIM_ENTRY_STRAFE_LEFT;
                } else if (condition->value[0] == ANIM_STRAFE_RIGHT) {
                    animation->flags |= BG_ANIM_ENTRY_STRAFE_RIGHT;
                }
                break;
            }
        }
    }

    if (bgAnimParseCurrentEvent == ANIM_EVENT_FIRE_WEAPON) {
        animation->flags |= BG_ANIM_ENTRY_FIRE_WEAPON;
        animation->blendTime = BG_ANIM_FIRE_WEAPON_BLEND_TIME_MS;
    } else if (bgAnimParseCurrentEvent == ANIM_EVENT_DEATH) {
        animation->moveSpeed = 0;
        animation->flags |= BG_ANIM_ENTRY_DEATH;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: duration/turret/blend option loop extracted from 0x1a9a3. */
/* VERIFIED_DECOMPILER(0x1a9a3, 2a9a3_BG_ParseCommands.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - duration, turretanim, blendtime, parse-error, static table flag/blend writes, unget fallback, and consumed-option loop checked against original option block. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_ParseCommands (0x1a9a3); no standalone original body. */
static void game_compat_bg_parse_command_options(char **parse, bg_anim_script_command_t *command,
                                   int bodySlot, bg_static_animation_table_t *table)
{
    qboolean consumedOption;

    do {
        consumedOption = 0;
        char *token = Com_ParseOnLine(parse);

        if (game_compat_bg_string_is_null_or_empty(token)) {
            Com_UngetToken();
            continue;
        }

        if (Q_stricmp(token, BG_PARSE_TOKEN_DURATION) == 0) {
            consumedOption = 1;
            token = Com_ParseOnLine(parse);
            if (game_compat_bg_string_is_null_or_empty(token)) {
                BG_AnimParseError(BG_PARSE_EXPECTED_DURATION_ERROR);
            }
            command->duration[bodySlot] = (int16_t)atoi(token);
        } else if (Q_stricmp(token, BG_PARSE_TOKEN_TURRETANIM) == 0) {
            consumedOption = 1;
            if (bgRuntimeAnimations == NULL) {
                table->entries[command->animIndex[bodySlot]].flags |=
                    BG_ANIM_ENTRY_TURRET;
            }
            if (command->bodyPart[bodySlot] != ANIM_BP_BOTH) {
                BG_AnimParseError(BG_PARSE_TURRET_BODY_ERROR);
            }
        } else if (Q_stricmp(token, BG_PARSE_TOKEN_BLENDTIME) == 0) {
            consumedOption = 1;
            token = Com_ParseOnLine(parse);
            if (game_compat_bg_string_is_null_or_empty(token)) {
                BG_AnimParseError(BG_PARSE_EXPECTED_BLENDTIME_ERROR);
            }
            if (bgRuntimeAnimations == NULL) {
                table->entries[command->animIndex[bodySlot]].blendTime = atoi(token);
            }
        } else {
            Com_UngetToken();
        }
    } while (consumedOption);
}

/* 0x1a9a3 BG_ParseCommands */
/* VERIFIED_DECOMPILER(0x1a9a3, 2a9a3_BG_ParseCommands.c, VERIFY-WAVE4-ANIMATION-C02-2026-06-17): DATAFLOW_VERIFIED - command token source selection, close-brace rewind, max-command error, four-byte bodyPart clear, body-part rewind/parameter tail, anim lookup, duration copy, static flag helper, option helper, body-slot transition, and sound tail helper checked. */
void BG_ParseCommands(char **parse, bg_anim_script_t *script,
                      bg_static_animation_table_t *table)
{
    bg_anim_script_command_t *command = NULL;
    int bodySlot = 0;

    while (1) {
        char *token = bodySlot < 1 ? Com_Parse(parse) : Com_ParseOnLine(parse);

        if (game_compat_bg_string_is_null_or_empty(token)) {
            return;
        }

        if (Q_stricmp(token, BG_PARSE_TOKEN_CLOSE_BRACE) == 0) {
            *parse = &(*parse)[-(ptrdiff_t)strlen(token)];
            return;
        }

        if (bodySlot == 0) {
            if (script->commandCount >= BG_ANIM_MAX_COMMANDS) {
                BG_AnimParseError(BG_PARSE_MAX_ANIMS_ERROR,
                                  BG_ANIM_MAX_COMMANDS);
            }
            command = &script->commands[script->commandCount];
            ++script->commandCount;
            memset(command->bodyPart, 0, sizeof(command->bodyPart));
        }

        command->bodyPart[bodySlot] =
            (int16_t)BG_IndexForString(token, animBodyPartsStr, 1);

        if (command->bodyPart[bodySlot] <= 0) {
            *parse = &(*parse)[-(ptrdiff_t)strlen(token)];
            game_compat_bg_parse_command_parameters(parse, command);
            bodySlot = 0;
            continue;
        }

        token = Com_ParseOnLine(parse);
        if (game_compat_bg_string_is_null_or_empty(token)) {
            BG_AnimParseError(BG_PARSE_EXPECTED_ANIM_ERROR);
        }

        command->animIndex[bodySlot] = (int16_t)BG_AnimationIndexForString(token);
        command->duration[bodySlot] =
            (int16_t)table->entries[command->animIndex[bodySlot]].duration;

        game_compat_bg_apply_static_command_flags(script, command, bodySlot, table);
        game_compat_bg_parse_command_options(parse, command, bodySlot, table);

        if (command->bodyPart[bodySlot] == ANIM_BP_BOTH || bodySlot > 0) {
            game_compat_bg_parse_command_parameters(parse, command);
            bodySlot = 0;
        } else {
            ++bodySlot;
        }
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: parser rewind/internal-check helper extracted from 0x1b042. */
/* VERIFIED_DECOMPILER(0x1b042, 2b042_BG_AnimParseAnimScript.c, VERIFY-PARSER-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against both parser rewind sites: strlen token rewind, strncmp over token length, and internal-error path. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_AnimParseAnimScript (0x1b042); no standalone original body. */
static void game_compat_bg_rewind_and_verify_token(char **parse, const char *token)
{
    const int length = (int)strlen(token);

    *parse = &(*parse)[-(ptrdiff_t)length];
    if (Q_strncmp(*parse, token, length) != 0) {
        BG_AnimParseError(BG_PARSE_INTERNAL_ERROR);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: script pool allocation/list append helper extracted from 0x1b042. */
/* VERIFIED_DECOMPILER(0x1b042, 2b042_BG_AnimParseAnimScript.c, VERIFY-PARSER-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against script allocation slices: per-list 0x80 guard, global 0x800 guard, pool offset, count increments, list append, and 0x10c copy. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_AnimParseAnimScript (0x1b042); no standalone original body. */
static bg_anim_script_t *game_compat_bg_alloc_anim_script(bg_static_animation_table_t *table,
                                            bg_anim_script_list_t *scriptList,
                                            const bg_anim_script_t *source)
{
    bg_anim_script_t *script;

    if (scriptList->count >= BG_ANIM_MAX_LIST_ITEMS) {
        BG_AnimParseError(BG_PARSE_MAX_LIST_ITEMS_ERROR,
                          BG_ANIM_MAX_LIST_ITEMS);
    }

    if (table->scriptPoolCount >= BG_ANIM_MAX_GLOBAL_SCRIPTS) {
        BG_AnimParseError(BG_PARSE_MAX_GLOBAL_ITEMS_ERROR,
                          BG_ANIM_MAX_GLOBAL_SCRIPTS);
    }

    script = &table->scriptPool[table->scriptPoolCount];
    ++table->scriptPoolCount;

    game_compat_bg_anim_script_list_append(scriptList, script);
    memcpy(script, source, sizeof(*script));

    return script;
}

/* NOT_FROM_ORIGINAL_SOURCE: scratch parse/copy helper extracted from 0x1b042. */
/* VERIFIED_DECOMPILER(0x1b042, 2b042_BG_AnimParseAnimScript.c, VERIFY-PARSER-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against both script-entry slices: token rewind verification, 0x10c scratch clear, condition parse into state slot, allocation, and copied script return. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_AnimParseAnimScript (0x1b042); no standalone original body. */
static bg_anim_script_t *game_compat_bg_parse_and_store_anim_script(char **parse,
                                                    bg_static_animation_table_t *table,
                                                    bg_anim_script_list_t *scriptList,
                                                    const char *token,
                                                    int *stateSlot)
{
    bg_anim_script_t scriptStorage;
    bg_anim_script_t *scratchScript = &scriptStorage;
    bg_anim_script_t *script;

    game_compat_bg_rewind_and_verify_token(parse, token);
    memset(&scriptStorage, 0, sizeof(scriptStorage));

    *stateSlot = BG_ParseConditions(parse, scratchScript);
    script = game_compat_bg_alloc_anim_script(table, scriptList, scratchScript);

    return script;
}

/* NOT_FROM_ORIGINAL_SOURCE: defines-section helper extracted from 0x1b042. */
/* VERIFIED_DECOMPILER(0x1b042, 2b042_BG_AnimParseAnimScript.c, VERIFY-PARSER-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against defines case: set-token gate, condition/alias/equal token errors, bitset-mode guard, alias string/hash stores, bit parser call, and count increment. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_AnimParseAnimScript (0x1b042); no standalone original body. */
static void game_compat_bg_parse_define_section(char **parse, const char *token)
{
    int conditionIndex;
    int aliasIndex;
    char *conditionName;
    char *aliasName;
    char *equalsToken;

    if (Q_stricmp(token, BG_PARSE_TOKEN_SET) != 0) {
        return;
    }

    conditionName = Com_ParseOnLine(parse);
    if (game_compat_bg_string_is_null_or_empty(conditionName)) {
        BG_AnimParseError(BG_PARSE_EXPECTED_CONDITION_TYPE_ERROR);
    }

    conditionIndex = BG_IndexForString(conditionName, bgAnimConditionTypeStrings, 0);
    if (bgAnimConditionTypes[conditionIndex].mode !=
        ANIM_CONDMODE_BITMASK) {
        BG_AnimParseError(BG_PARSE_DEFINE_TYPE_ERROR, conditionName);
    }

    aliasName = Com_ParseOnLine(parse);
    if (game_compat_bg_string_is_null_or_empty(aliasName)) {
        BG_AnimParseError(BG_PARSE_EXPECTED_CONDITION_DEFINE_ERROR);
    }

    /* ORIGINAL_BINARY_BUG: 0x1b394..0x1b4c9 never checks aliasIndex against
     * the 16-entry rows before writing names, hashes, masks, and the count.
     * Preserve it; see
     * original-bugs/game-animation-condition-define-table-overflow.md. */
    aliasIndex = bgAnimConditionAliasCounts[conditionIndex];
    bgAnimConditionAliases[conditionIndex][aliasIndex].name =
        BG_CopyStringIntoBuffer(
            aliasName, bgAnimConditionAliasStringBuffer,
            BG_ANIM_CONDITION_ALIAS_STRING_BUFFER_SIZE,
            &bgAnimConditionAliasStringUsed);
    bgAnimConditionAliases[conditionIndex][aliasIndex].hash =
        BG_StringHashValue(bgAnimConditionAliases[conditionIndex][aliasIndex].name);

    equalsToken = Com_ParseOnLine(parse);
    if (equalsToken == NULL) {
        BG_AnimParseError(BG_PARSE_EXPECTED_EQUALS_EOF_ERROR);
    }

    if (Q_stricmp(equalsToken, BG_PARSE_TOKEN_EQUALS) != 0) {
        BG_AnimParseError(BG_PARSE_EXPECTED_EQUALS_TOKEN_ERROR, equalsToken);
    }

    BG_ParseConditionBits(parse, bgAnimConditionTypes[conditionIndex].values,
                          conditionIndex, bgAnimConditionAliasBits[conditionIndex][aliasIndex]);
    ++bgAnimConditionAliasCounts[conditionIndex];
}

/* NOT_FROM_ORIGINAL_SOURCE: state-index initialization helper extracted from 0x1b042. */
/* VERIFIED_DECOMPILER(0x1b042, 2b042_BG_AnimParseAnimScript.c, VERIFY-PARSER-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against local state-index loop: indexes 0..2 initialized to -1 before parsing. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_AnimParseAnimScript (0x1b042); no standalone original body. */
static void game_compat_bg_clear_parser_state_indexes(int *stateIndexes)
{
    for (int index = 0; index < 3; ++index) {
        stateIndexes[index] = -1;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: animations/canned section state-machine helper extracted from 0x1b042. */
/* VERIFIED_DECOMPILER(0x1b042, 2b042_BG_AnimParseAnimScript.c, VERIFY-PARSER-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against animations/canned switch cases: brace depth rules, state/group/script/command levels, list offset selection, anim-group global, list zeroing, and parse-command handoff. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_AnimParseAnimScript (0x1b042); no standalone original body. */
static void game_compat_bg_handle_animation_section_token(bg_static_animation_table_t *table,
                                           bg_anim_parse_section_t section,
                                           char **parse, char *token, int *depth,
                                           int *stateIndexes,
                                           bg_anim_script_list_t **activeList,
                                           bg_anim_script_t **activeScript)
{
    if (Q_stricmp(token, BG_PARSE_TOKEN_OPEN_BRACE) == 0) {
        if (*depth > 2 || stateIndexes[*depth] < 0) {
            BG_AnimParseError(BG_PARSE_UNEXPECTED_TOKEN_ERROR, token);
        }
        ++*depth;
        return;
    }

    if (Q_stricmp(token, BG_PARSE_TOKEN_CLOSE_BRACE) == 0) {
        --*depth;
        if (*depth < 0) {
            BG_AnimParseError(BG_PARSE_UNEXPECTED_TOKEN_ERROR, token);
        }
        if (*depth == 1) {
            *activeList = NULL;
        }
        stateIndexes[*depth] = -1;
        return;
    }

    if (*depth == 0 && stateIndexes[0] < 0) {
        char *stateName;
        char *openBrace;

        if (Q_stricmp(token, BG_PARSE_TOKEN_STATE) != 0) {
            BG_AnimParseError(BG_PARSE_EXPECTED_STATE_ERROR);
        }

        stateName = Com_ParseOnLine(parse);
        if (stateName == NULL) {
            BG_AnimParseError(BG_PARSE_EXPECTED_STATE_TYPE_ERROR);
        }

        stateIndexes[0] = BG_IndexForString(stateName, animStateStr, 0);
        openBrace = Com_Parse(parse);
        if (game_compat_bg_string_is_null_or_empty(openBrace) ||
            Q_stricmp(openBrace, BG_PARSE_TOKEN_OPEN_BRACE) != 0) {
            BG_AnimParseError(BG_PARSE_EXPECTED_OPEN_BRACE_ERROR);
        }
        ++*depth;
        return;
    }

    if (*depth == 1 && stateIndexes[1] < 0) {
        stateIndexes[1] = BG_IndexForString(token, bgAnimGroupStrings, 0);
        if (section == ANIM_SECTION_ANIMATIONS) {
            *activeList = game_compat_bg_anim_script_animation_list(table, stateIndexes[0],
                                                     stateIndexes[1]);
            bgAnimParseCurrentAnimGroup = stateIndexes[1];
        } else {
            *activeList = game_compat_bg_anim_script_canned_animation_list(table, stateIndexes[0],
                                                           stateIndexes[1]);
        }
        memset(*activeList, 0, sizeof(**activeList));
        return;
    }

    if (*depth == 2 && stateIndexes[2] < 0) {
        *activeScript = game_compat_bg_parse_and_store_anim_script(parse, table, *activeList, token,
                                                   &stateIndexes[2]);
        return;
    }

    if (*depth == 3) {
        game_compat_bg_rewind_and_verify_token(parse, token);
        BG_ParseCommands(parse, *activeScript, table);
        return;
    }

    BG_AnimParseError(BG_PARSE_UNEXPECTED_TOKEN_ERROR, token);
}

/* NOT_FROM_ORIGINAL_SOURCE: statechange/event section state-machine helper extracted from 0x1b042. */
/* VERIFIED_DECOMPILER(0x1b042, 2b042_BG_AnimParseAnimScript.c, VERIFY-PARSER-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against statechange/event switch cases: brace depth rules, to/from state ordering, event global, list offset selection, list zeroing, script parse, and command handoff. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_AnimParseAnimScript (0x1b042); no standalone original body. */
static void game_compat_bg_handle_state_change_or_event_token(bg_static_animation_table_t *table,
                                             bg_anim_parse_section_t section,
                                             char **parse, char *token, int *depth,
                                             int *stateIndexes,
                                             bg_anim_script_list_t **activeList,
                                             bg_anim_script_t **activeScript)
{
    if (Q_stricmp(token, BG_PARSE_TOKEN_OPEN_BRACE) == 0) {
        if (*depth > 2 || stateIndexes[*depth] < 0) {
            BG_AnimParseError(BG_PARSE_UNEXPECTED_TOKEN_ERROR, token);
        }
        ++*depth;
        return;
    }

    if (Q_stricmp(token, BG_PARSE_TOKEN_CLOSE_BRACE) == 0) {
        --*depth;
        if (*depth < 0) {
            BG_AnimParseError(BG_PARSE_UNEXPECTED_TOKEN_ERROR, token);
        }
        if (*depth == 0) {
            *activeList = NULL;
        }
        stateIndexes[*depth] = -1;
        return;
    }

    if (*depth == 0 && stateIndexes[0] < 0) {
        if (section == ANIM_SECTION_STATECHANGES) {
            char *stateName;
            char *openBrace;
            int toState;
            int fromState;

            if (Q_stricmp(token, BG_PARSE_TOKEN_STATECHANGE) != 0) {
                BG_AnimParseError(BG_PARSE_EXPECTED_STATECHANGE_ERROR, token);
            }

            stateName = Com_ParseOnLine(parse);
            if (stateName == NULL) {
                BG_AnimParseError(BG_PARSE_EXPECTED_STATECHANGE_TYPE_ERROR);
            }
            toState = BG_IndexForString(stateName, animStateStr, 0);

            stateName = Com_ParseOnLine(parse);
            if (stateName == NULL) {
                BG_AnimParseError(BG_PARSE_EXPECTED_STATECHANGE_TYPE_ERROR);
            }
            fromState = BG_IndexForString(stateName, animStateStr, 0);

            stateIndexes[0] = fromState;
            *activeList = game_compat_bg_anim_script_state_change_list(table, fromState, toState);

            openBrace = Com_Parse(parse);
            if (game_compat_bg_string_is_null_or_empty(openBrace) ||
                Q_stricmp(openBrace, BG_PARSE_TOKEN_OPEN_BRACE) != 0) {
                BG_AnimParseError(BG_PARSE_EXPECTED_OPEN_BRACE_ERROR);
            }
            ++*depth;
        } else {
            stateIndexes[0] = BG_IndexForString(token, bgAnimEventStrings, 0);
            *activeList = game_compat_bg_anim_script_event_list(
                table, (bg_anim_event_t)stateIndexes[0]);
            bgAnimParseCurrentEvent = (bg_anim_event_t)stateIndexes[0];
        }

        memset(*activeList, 0, sizeof(**activeList));
        return;
    }

    if (*depth == 1 && stateIndexes[1] < 0) {
        *activeScript = game_compat_bg_parse_and_store_anim_script(parse, table, *activeList, token,
                                                   &stateIndexes[1]);
        return;
    }

    if (*depth == 2) {
        game_compat_bg_rewind_and_verify_token(parse, token);
        BG_ParseCommands(parse, *activeScript, table);
        return;
    }

    BG_AnimParseError(BG_PARSE_UNEXPECTED_TOKEN_ERROR, token);
}

/* 0x1b042 BG_AnimParseAnimScript */
/* VERIFIED_DECOMPILER(0x1b042, 2b042_BG_AnimParseAnimScript.c, VERIFY-PARSER-PACKET-2026-06-17): DATAFLOW_VERIFIED - file load guard, globals, parser scratch clears, parse session, section dispatch, EOF depth error, script-path clear, and helper side effects checked. */
void BG_AnimParseAnimScript(bg_static_animation_table_t *table,
                            bg_runtime_animation_t *runtimeAnimations,
                            int32_t *runtimeAnimationCount)
{
    char *parse;
    int currentSection = ANIM_SECTION_DEFINES;
    int depth = 0;
    int stateIndexes[3];
    bg_anim_script_list_t *activeList = NULL;
    bg_anim_script_t *activeScript = NULL;

    if (bgAnimScriptLoaded == 0) {
        int handle;
        const int fileLength =
            trap_FS_FOpenFile(bgPlayerAnimScriptPath, &handle, 0);

        if (fileLength < 1 ||
            fileLength > BG_ANIM_SCRIPT_FILE_MAX_BYTES) {
            Com_Error(ERROR_DROP, BG_PARSE_SCRIPT_LOAD_ERROR,
                      bgPlayerAnimScriptPath);
        }

        trap_FS_Read(bgAnimScriptFileBuffer, fileLength, handle);
        bgAnimScriptFileBuffer[fileLength] = '\0';
        trap_FS_FCloseFile(handle);
        bgAnimScriptLoaded = 1;
    }

    bgAnimStaticTable = table;
    bgRuntimeAnimations = runtimeAnimations;
    bgRuntimeAnimationCount = runtimeAnimationCount;

    BG_InitWeaponStrings();
    memset(bgAnimConditionAliases, 0, ANIM_COND_COUNT *
                                  BG_ANIM_CONDITION_ALIAS_ENTRIES *
                                  sizeof(bgAnimConditionAliases[0][0]));
    memset(bgAnimConditionAliasStringBuffer, 0,
           sizeof(bgAnimConditionAliasStringBuffer));
    memset(bgAnimConditionAliasCounts, 0, ANIM_COND_COUNT * sizeof(bgAnimConditionAliasCounts[0]));
    bgAnimConditionAliasStringUsed = 0;
    game_compat_bg_clear_parser_state_indexes(stateIndexes);

    parse = bgAnimScriptFileBuffer;
    Com_BeginParseSession("BG_AnimParseAnimScript");

    while (1) {
        char *token = Com_Parse(&parse);
        int section;

        if (game_compat_bg_string_is_null_or_empty(token)) {
            break;
        }

        section = BG_IndexForString(token, bgAnimParseSectionStrings, 1);
        if (section >= 0) {
            if (depth != 0) {
                BG_AnimParseError(BG_PARSE_UNEXPECTED_TOKEN_ERROR, token);
            }
            currentSection = section;
            bgAnimParseCurrentAnimGroup = 0;
            bgAnimParseCurrentEvent = ANIM_EVENT_NONE;
            continue;
        }

        if (currentSection == ANIM_SECTION_DEFINES) {
            game_compat_bg_parse_define_section(&parse, token);
        } else if (currentSection == ANIM_SECTION_ANIMATIONS ||
                   currentSection == ANIM_SECTION_CANNED_ANIMATIONS) {
            game_compat_bg_handle_animation_section_token(table, (bg_anim_parse_section_t)currentSection,
                                           &parse, token, &depth, stateIndexes,
                                           &activeList, &activeScript);
        } else if (currentSection == ANIM_SECTION_STATECHANGES ||
                   currentSection == ANIM_SECTION_EVENTS) {
            game_compat_bg_handle_state_change_or_event_token(
                table, (bg_anim_parse_section_t)currentSection, &parse, token,
                &depth, stateIndexes, &activeList, &activeScript);
        }
    }

    if (depth != 0) {
        /* ORIGINAL_BINARY_BUG: 0x1b241..0x1b24a supplies the `%s` format but
         * no string argument.  Preserve the missing vararg; see
         * original-bugs/game-animparse-eof-error-missing-vararg.md. */
        BG_AnimParseError(BG_PARSE_UNEXPECTED_EOF_ERROR);
    }

    bgPlayerAnimScriptPath = NULL;
    Com_EndParseSession();
}


/* 0x1be6c BG_EvaluateConditions */
/* VERIFIED_DECOMPILER(0x1be6c, 2be6c_FUN_0002be6c.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - condition-count loop, 12-byte condition records, bitset two-word intersection, value-mode word equality, and early false/true returns checked. */
static qboolean BG_EvaluateConditions(clientInfo_t *clientInfo,
                                const bg_anim_script_t *script)
{
    for (int index = 0; index < script->conditionCount; ++index) {
        const bg_anim_condition_t *condition = &script->conditions[index];
        const bg_anim_condition_type_t *conditionType = &bgAnimConditionTypes[condition->type];
        uint32_t *stateWords = game_compat_bg_player_anim_condition_words(clientInfo,
                                                           condition->type);

        if (conditionType->mode == ANIM_CONDMODE_BITMASK) {
            if (((uint32_t)condition->value[0] & stateWords[0]) == 0 &&
                ((uint32_t)condition->value[1] & stateWords[1]) == 0) {
                return 0;
            }
        } else if (conditionType->mode == ANIM_CONDMODE_EQUAL &&
                   stateWords[0] !=
                   (uint32_t)condition->value[0]) {
            return 0;
        }
    }

    return 1;
}

/* 0x1bf35 BG_FirstValidItem */
/* VERIFIED_DECOMPILER(0x1bf35, 2bf35_FUN_0002bf35.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - client condition-state stride, script-list count scan, offset dereference helper, match call, and NULL/no-match return checked. */
static bg_anim_script_t *BG_FirstValidItem(int stateIndex,
                                            const bg_anim_script_list_t *scriptList)
{
    clientInfo_t *clientInfo =
        game_compat_bg_player_anim_client_info(bgAnimStaticTable, stateIndex);

    for (int index = 0; index < scriptList->count; ++index) {
        bg_anim_script_t *script = game_compat_bg_anim_script_list_get(scriptList, index);

        if (BG_EvaluateConditions(clientInfo, script)) {
            return script;
        }
    }

    return NULL;
}

/* NOT_FROM_ORIGINAL_SOURCE: command duration helper extracted from 0x1c1a9. */
/* VERIFIED_DECOMPILER(0x1c1a9, 2c1a9_BG_ExecuteCommand.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against both command slots using duration[0] plus 50ms timer pad. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_ExecuteCommand (0x1c1a9); no standalone original body. */
static int game_compat_bg_animation_command_duration(const bg_anim_script_command_t *command)
{
    return (int)command->duration[0] + BG_ANIM_TIMER_PAD_MS;
}

/* NOT_FROM_ORIGINAL_SOURCE: slot word/timer helper extracted from 0x1bfb9. */
/* VERIFIED_DECOMPILER(0x1bfb9, 2bfb9_BG_PlayAnim.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against legs/torso slot blocks: timer threshold/force gate, restart-same index mask, 0x200 toggle, setTimer store, looped refresh, and no-start return. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_PlayAnim (0x1bfb9); no standalone original body. */
static int game_compat_bg_play_anim_slot(uint32_t animationIndex, int duration,
                           qboolean setTimer, qboolean restartSame,
                           qboolean force, int32_t *timer, int32_t *slot)
{
    if (*timer >= BG_ANIM_RESTART_MIN_TIMER_MS && !force) {
        return 0;
    }

    /* Decompiled BG_PlayAnim updates the 32-bit slot word at the supplied address. */
    const uint32_t slotWord = (uint32_t)*slot;
    if (!restartSame || ((slotWord & ~ANIM_TOGGLEBIT) != animationIndex)) {
        *slot = (int32_t)(((slotWord & ANIM_TOGGLEBIT) ^
                           ANIM_TOGGLEBIT) | animationIndex);
        if (setTimer) {
            *timer = duration;
        }
        return 1;
    }

    if (setTimer &&
        (bgAnimStaticTable->entries[animationIndex].flags & BG_ANIM_ENTRY_LOOPED) != 0) {
        *timer = duration;
    }

    return 0;
}

/* 0x1bfb9 BG_PlayAnim */
/* VERIFIED_DECOMPILER(0x1bfb9, 2bfb9_BG_PlayAnim.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - default duration, legs/body-part dispatch, both-part torso zero animation, torso dispatch, and legs-start duration/-1 return checked. */
int BG_PlayAnim(playerState_t *player, uint32_t animationIndex,
                uint32_t bodyPart, int duration, qboolean setTimer,
                qboolean restartSame, qboolean force)
{
    qboolean legsStarted = 0;

    if (duration == 0) {
        duration = bgAnimStaticTable->entries[animationIndex].duration +
                   BG_ANIM_TIMER_PAD_MS;
    }

    if (bodyPart == ANIM_BP_LEGS || bodyPart == ANIM_BP_BOTH) {
        legsStarted = game_compat_bg_play_anim_slot(animationIndex, duration, setTimer,
                                      restartSame, force, &player->legsTimer,
                                      &player->legsAnim);

        if (bodyPart == ANIM_BP_BOTH) {
            animationIndex = 0;
        }
    }

    if (bodyPart == ANIM_BP_TORSO || bodyPart == ANIM_BP_BOTH) {
        (void)game_compat_bg_play_anim_slot(animationIndex, duration, setTimer, restartSame, force,
                              &player->torsoTimer,
                              &player->torsoAnim);
    }

    return legsStarted ? duration : -1;
}

/* 0x1c151 BG_PlayAnimName */
/* VERIFIED_DECOMPILER(0x1c151, 2c151_BG_PlayAnimName.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - animation-name lookup, BG_PlayAnim argument order, zero duration, flag forwarding, and void return checked. */
void BG_PlayAnimName(playerState_t *player, const char *animationName,
                     uint32_t bodyPart, qboolean setTimer, qboolean restartSame,
                     qboolean force)
{
    const uint32_t animationIndex = (uint32_t)BG_AnimationIndexForString(animationName);

    (void)BG_PlayAnim(player, animationIndex, bodyPart, 0, setTimer, restartSame,
                      force);
}

/* NOT_FROM_ORIGINAL_SOURCE: duration-tracking predicate extracted from 0x1c1a9. */
/* ORIGINAL_BINARY_BUG: 0x1c294..0x1c2a4 tests bodyPart[0] while deciding
 * whether the second command's result tracks duration; preserve that first-
 * slot predicate for both command slots.  See
 * original-bugs/game-bg-execute-command-second-slot-predicate.md. */
/* VERIFIED_DECOMPILER(0x1c1a9, 2c1a9_BG_ExecuteCommand.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against original first-slot body-part predicate; the second command block still tests bodyPart[0]. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_ExecuteCommand (0x1c1a9); no standalone original body. */
static qboolean game_compat_bg_command_tracks_duration(const bg_anim_script_command_t *command)
{
    return command->bodyPart[0] == ANIM_BP_LEGS ||
           command->bodyPart[0] == ANIM_BP_BOTH;
}

/* 0x1c1a9 BG_ExecuteCommand */
/* VERIFIED_DECOMPILER(0x1c1a9, 2c1a9_BG_ExecuteCommand.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - two body-part guards, duration[0]+50 for both slots, animIndex/bodyPart call order, first-slot duration tracking predicate, sound callback args, and duration/-1 return checked. */
int BG_ExecuteCommand(playerState_t *player,
                      const bg_anim_script_command_t *command,
                      qboolean setTimer, qboolean restartSame, qboolean force)
{
    int duration = -1;
    qboolean trackedStarted = 0;

    if (command->bodyPart[0] != 0) {
        duration = game_compat_bg_animation_command_duration(command);

        const int result = BG_PlayAnim(player, (uint32_t)command->animIndex[0],
                                       (uint32_t)command->bodyPart[0], duration,
                                       setTimer, restartSame, force);
        if (game_compat_bg_command_tracks_duration(command)) {
            /* 0x1c233: machine computes (~result) >> 31, i.e. result >= 0,
             * not result != -1. */
            trackedStarted = result >= 0;
        }
    }

    if (command->bodyPart[1] != 0) {
        duration = game_compat_bg_animation_command_duration(command);

        const int result = BG_PlayAnim(player, (uint32_t)command->animIndex[1],
                                       (uint32_t)command->bodyPart[1], duration,
                                       setTimer, restartSame, force);
        if (game_compat_bg_command_tracks_duration(command)) {
            /* 0x1c233: machine computes (~result) >> 31, i.e. result >= 0,
             * not result != -1. */
            trackedStarted = result >= 0;
        }
    }

    if (command->soundAliasName != NULL) {
        game_compat_bg_anim_sound_event(game_compat_bg_player_anim_script_client_num(player),
                          command->soundAliasName);
    }

    return trackedStarted ? duration : -1;
}

/* 0x1c377 BG_AnimScriptAnimation */
/* VERIFIED_DECOMPILER(0x1c377, 2c377_BG_AnimScriptAnimation.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - pmType guard, state fallback loop, animation-list address, match lookup, command-count guard, anim-group condition update, clientNum modulo command pick, execute flags, and 0xffffffff/boolean return checked. */
uint32_t BG_AnimScriptAnimation(playerState_t *player, int stateIndex,
                                int animGroup, qboolean restartSame)
{
    bg_anim_script_t *script = NULL;
    const int clientNum = game_compat_bg_player_anim_script_client_num(player);

    if (player->pmType >= PM_TYPE_ANIM_SCRIPT_LIMIT) {
        return UINT32_MAX;
    }

    while (script == NULL && stateIndex >= 0) {
        bg_anim_script_list_t *scriptList =
            game_compat_bg_anim_script_animation_list(bgAnimStaticTable, stateIndex, animGroup);

        if (scriptList->count != 0) {
            script = BG_FirstValidItem(clientNum, scriptList);
        }

        if (script == NULL) {
            --stateIndex;
        }
    }

    if (script == NULL || script->commandCount == 0) {
        return UINT32_MAX;
    }

    BG_UpdateConditionValue(clientNum, ANIM_COND_MOVETYPE,
                            animGroup, 1);

    const bg_anim_script_command_t *command =
        &script->commands[clientNum % script->commandCount];
    return BG_ExecuteCommand(player, command, 0, restartSame, 0) != -1 ? 1u : 0u;
}

/* 0x1c4e3 BG_AnimScriptStateChange */
/* VERIFIED_DECOMPILER(0x1c4e3, 2c4e3_BG_AnimScriptStateChange.c, VERIFY-ANIM-COMMAND-PACKET-2026-06-17): DATAFLOW_VERIFIED - pmType guard, to-state/from-state list address, empty-list return, match lookup, command-count guard, rand modulo command pick, execute flags, and -1 return paths checked. */
int BG_AnimScriptStateChange(playerState_t *player, int fromState,
                             int toState)
{
    const int clientNum = game_compat_bg_player_anim_script_client_num(player);

    if (player->pmType >= PM_TYPE_ANIM_SCRIPT_LIMIT) {
        return -1;
    }

    bg_anim_script_list_t *scriptList =
        game_compat_bg_anim_script_state_change_list(bgAnimStaticTable, fromState, toState);
    if (scriptList->count == 0) {
        return -1;
    }

    bg_anim_script_t *script =
        BG_FirstValidItem(clientNum, scriptList);
    if (script == NULL || script->commandCount == 0) {
        return -1;
    }

    return BG_ExecuteCommand(player, game_compat_bg_select_random_anim_script_command(script), 1, 0,
                             0);
}

/* 0x1c5e3 BG_AnimScriptEvent */
/* VERIFIED_DECOMPILER(0x1c5e3, 2c5e3_BG_AnimScriptEvent.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - pmType/event-1 gate, event-list address, match lookup, command-count guard, rand command selection, execute flags/argument order, and -1 returns checked. */
int BG_AnimScriptEvent(playerState_t *player, bg_anim_event_t event,
                       qboolean restartSame, qboolean force)
{
    const int clientNum = game_compat_bg_player_anim_script_client_num(player);

    if (event != ANIM_EVENT_DEATH &&
        player->pmType >= PM_TYPE_ANIM_SCRIPT_LIMIT) {
        return -1;
    }

    bg_anim_script_list_t *scriptList =
        game_compat_bg_anim_script_event_list(bgAnimStaticTable, event);
    if (scriptList->count == 0) {
        return -1;
    }

    bg_anim_script_t *script =
        BG_FirstValidItem(clientNum, scriptList);
    if (script == NULL || script->commandCount == 0) {
        return -1;
    }

    return BG_ExecuteCommand(player, game_compat_bg_select_random_anim_script_command(script), 1,
                             restartSame, force);
}

/* 0x1c6d5 BG_GetAnimString */
/* VERIFIED_DECOMPILER(0x1c6d5, 2c6d5_BG_GetAnimString.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - static-table count range check, parse-error path, ignored client argument, and entry-name pointer return checked. */
char *BG_GetAnimString(int unusedClientNum, uint32_t animationIndex)
{
    (void)unusedClientNum;

    if ((uint32_t)bgAnimStaticTable->count <= animationIndex) {
        BG_AnimParseError("BG_GetAnimString: anim index is out of range");
    }

    return bgAnimStaticTable->entries[animationIndex].name;
}

/* 0x1c718 BG_UpdateConditionValue */
/* VERIFIED_DECOMPILER(0x1c718, 2c718_BG_UpdateConditionValue.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - client condition-state stride, scalar-vs-bitset mode branch, checkConversion gate, two-word clear, and Com_BitSet target checked. */
void BG_UpdateConditionValue(int clientNum, int conditionType, int value,
                             int checkConversion)
{
    uint32_t *conditionWords = game_compat_bg_player_anim_condition_words(
        game_compat_bg_player_anim_client_info(bgAnimStaticTable, clientNum), conditionType);

    if (!checkConversion ||
        !game_compat_bg_anim_condition_uses_bitmask(conditionType)) {
        conditionWords[0] = (uint32_t)value;
    } else {
        conditionWords[0] = 0;
        conditionWords[1] = 0;
        Com_BitSet(conditionWords, value);
    }
}

/* 0x1c7ce FUN_0002c7ce */
/* VERIFIED_DECOMPILER(0x1c7ce, 2c7ce_FUN_0002c7ce.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - condition word load, convert-bitset mode guard, 0..63 Com_BitCheck loop, first-bit return, zero fallback, and scalar return checked. */
static uint32_t BG_GetConditionValue(clientInfo_t *clientInfo, int conditionType,
                              qboolean convertBitset)
{
    const uint32_t *conditionWords =
        game_compat_bg_player_anim_condition_words(clientInfo, conditionType);

    if (convertBitset &&
        game_compat_bg_anim_condition_uses_bitmask(conditionType)) {
        for (uint32_t bit = 0;
             bit < sizeof(((clientInfo_t *)0)->conditionWords[0]) * CHAR_BIT;
             ++bit) {
            if (Com_BitCheck(conditionWords, (int)bit) != 0) {
                return bit;
            }
        }

        return 0;
    }

    return conditionWords[0];
}

/* 0x1c85a BG_GetAnimScriptEvent */
/* VERIFIED_DECOMPILER(0x1c85a, 2c85a_BG_GetAnimScriptEvent.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - pmType/event-1 gate, event-list address, match lookup, command-count guard, rand command selection, animIndex[0] return, and -1 returns checked. */
int BG_GetAnimScriptEvent(playerState_t *player, bg_anim_event_t event)
{
    const int clientNum = game_compat_bg_player_anim_script_client_num(player);

    if (event != ANIM_EVENT_DEATH &&
        player->pmType >= PM_TYPE_ANIM_SCRIPT_LIMIT) {
        return -1;
    }

    bg_anim_script_list_t *scriptList =
        game_compat_bg_anim_script_event_list(bgAnimStaticTable, event);
    if (scriptList->count == 0) {
        return -1;
    }

    bg_anim_script_t *script =
        BG_FirstValidItem(clientNum, scriptList);
    if (script == NULL || script->commandCount == 0) {
        return -1;
    }

    return game_compat_bg_select_random_anim_script_command(script)->animIndex[0];
}

/* 0x1c928 BG_GetAnimationForIndex */
/* VERIFIED_DECOMPILER(0x1c928, 2c928_BG_GetAnimationForIndex.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - static-table count range check, Com_Error drop path, ignored client argument, and static-entry pointer return checked. */
bg_static_animation_t *BG_GetAnimationForIndex(int unusedClientNum,
                                              uint32_t animationIndex)
{
    (void)unusedClientNum;

    if ((uint32_t)bgAnimStaticTable->count <= animationIndex) {
        Com_Error(ERROR_DROP,
                  COM_ERROR_MARKER
                  "BG_GetAnimationForIndex: index out of bounds");
    }

    return &bgAnimStaticTable->entries[animationIndex];
}

/* 0x1c973 BG_AnimUpdatePlayerStateConditions */
/* VERIFIED_DECOMPILER(0x1c973, 2c973_BG_AnimUpdatePlayerStateConditions.c, VERIFY-WAVE2-ANIMATION-STANCE-2026-06-17): DATAFLOW_VERIFIED
 * Evidence: matched weapon/class, ps flag 0x100, vehicle position/type/pitch
 * remaps and error path, movement fallback, view pitch, and input suppression.
 */
void BG_AnimUpdatePlayerStateConditions(pmove_t *pmove)
{
    playerState_t *ps = pmove->ps;
    const int clientNum = ps->psClientNum;

    game_compat_bg_update_anim_condition(clientNum, ANIM_COND_WEAPON, ps->currentWeapon);
    game_compat_bg_update_anim_condition(
        clientNum, ANIM_COND_WEAPONCLASS,
        ((const weaponInfo_t *)BG_GetInfoForWeapon(ps->currentWeapon))->weaponClass);
    game_compat_bg_update_anim_condition(
        clientNum, ANIM_COND_WEAPON_POSITION,
        (ps->entityStateFlags & BG_PS_FLAG_100) != 0
            ? ANIM_WEAPON_POSITION_ADS
            : ANIM_WEAPON_POSITION_HIP);

    if ((ps->entityStateFlags & BG_PS_FLAG_IN_VEHICLE) != 0) {
        game_compat_bg_update_vehicle_position_condition(ps);
        game_compat_bg_update_vehicle_type_condition(ps);
        game_compat_bg_update_anim_condition(clientNum, ANIM_COND_VEHICLE_MOTION,
                               ps->vehicleMotion);
    } else {
        game_compat_bg_update_anim_condition(
            clientNum, ANIM_COND_MOUNTED,
            (ps->entityStateFlags & BG_PS_FLAG_MOVE_CONDITION_MASK) != 0
                ? ANIM_MOUNT_MG42
                : ANIM_MOUNT_UNUSED);
        game_compat_bg_update_anim_condition(
            clientNum, ANIM_COND_VEHICLE, ANIM_VEHICLE_UNUSED);
        game_compat_bg_update_anim_condition(
            clientNum, ANIM_COND_VEHICLE_MOTION,
            ANIM_VEHICLE_MOTION_UNUSED);
    }

    game_compat_bg_update_anim_condition(clientNum, ANIM_COND_UNDERHAND,
                           ps->viewAngles[0] > 0.0f);

    qboolean inputCondition =
        (pmove->command.buttons & BG_PMOVE_INPUT_CONDITION_BIT) != 0;
    if ((ps->playerStateFlags & BG_STANCE_FLAG_SUPPRESS_INPUT_CONDITION) != 0) {
        inputCondition = 0;
    }

    game_compat_bg_update_anim_condition(clientNum, ANIM_COND_FIRING, inputCondition);
}

/* 0x1cdac BG_IsCrouchingAnim */
/* VERIFIED_DECOMPILER(0x1cdac, 2cdac_BG_IsCrouchingAnim.c, VERIFY-WAVE2-ANIMATION-STANCE-2026-06-17): DATAFLOW_VERIFIED
 * Evidence: clientNum at player info +0x08, animation toggle-bit clear, and
 * stateFlags mask 0x000000c4 match.
 */
qboolean BG_IsCrouchingAnim(const clientInfo_t *playerInfo,
                           uint32_t animationIndex)
{
    bg_static_animation_t *animation = BG_GetAnimationForIndex(
        playerInfo->clientNum, animationIndex & ~ANIM_TOGGLEBIT);

    return (animation->stateFlags & BG_ANIM_CROUCH_STATE_MASK) != 0;
}

/* 0x1ce04 BG_IsProneAnim */
/* VERIFIED_DECOMPILER(0x1ce04, 2ce04_BG_IsProneAnim.c, VERIFY-WAVE2-ANIMATION-STANCE-2026-06-17): DATAFLOW_VERIFIED
 * Evidence: clientNum at player info +0x08, animation toggle-bit clear, and
 * stateFlags mask 0x00000308 match.
 */
qboolean BG_IsProneAnim(const clientInfo_t *playerInfo,
                       uint32_t animationIndex)
{
    bg_static_animation_t *animation = BG_GetAnimationForIndex(
        playerInfo->clientNum, animationIndex & ~ANIM_TOGGLEBIT);

    return (animation->stateFlags & BG_ANIM_PRONE_STATE_MASK) != 0;
}

/* 0x1d922 BG_PlayerAnimation_VerifyAnim */
/* VERIFIED_DECOMPILER(0x1d922, 2d922_FUN_0002d922.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - anims index packing with slot low word, toggle-bit mask, zero-weight compare, and slot word/offset/blend-time stores checked. */
static void BG_PlayerAnimation_VerifyAnim(XAnimTree *animTree,
                                          bg_anim_slot_t *slot)
{
    const uint16_t animsIndex =
        Scr_GetAnimsIndex(bgAnimStaticTable->anims);

    if (slot->animationWord != 0) {
        const uint32_t anim =
            game_compat_bg_make_xanim(animsIndex, (uint16_t)slot->animationWord) &
            ~ANIM_TOGGLEBIT;

        if (trap_XAnimGetWeight(animTree, anim) == 0.0f) {
            slot->animationWord = 0;
            game_compat_bg_set_anim_slot_animation(slot, NULL);
            slot->blendTime = BG_ANIM_RESET_BLEND_TIME_MS;
        }
    }
}

/* 0x1d9b4 BG_SwingAngles */
/* VERIFIED_DECOMPILER(0x1d9b4, 2d9b4_FUN_0002d9b4.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - activation threshold, step-scale floor, positive/negative step branches, active-flag stores, AngleMod update, and clamp stores checked. */
static void BG_SwingAngles(float targetAngle, float startThreshold,
                           float clampDelta,
                      float rateScale, float *currentAngle, qboolean *active)
{
    if (!*active) {
        const float delta = AngleSubtract(*currentAngle, targetAngle);

        if (delta > startThreshold || delta < -startThreshold) {
            *active = 1;
        }
    }

    if (*active) {
        float delta = AngleSubtract(targetAngle, *currentAngle);
        float stepScale = fabsf(delta) * BG_ANIM_ANGLE_STEP_SCALE;

        if (stepScale < BG_ANIM_ANGLE_MIN_STEP) {
            stepScale = BG_ANIM_ANGLE_MIN_STEP;
        }

        if (delta >= 0.0f) {
            const float maxStep = (float)bg.frameTime * stepScale * rateScale;

            if (delta <= maxStep) {
                *active = 0;
            } else {
                *active = 1;
                delta = maxStep;
            }
            *currentAngle = AngleMod(*currentAngle + delta);
        } else if (delta < 0.0f) {
            const float minStep = -rateScale * (float)bg.frameTime * stepScale;

            if (minStep <= delta) {
                *active = 0;
            } else {
                *active = 1;
                delta = minStep;
            }
            *currentAngle = AngleMod(*currentAngle + delta);
        }

        delta = AngleSubtract(targetAngle, *currentAngle);
        if (delta > clampDelta) {
            *currentAngle = AngleMod(targetAngle - clampDelta);
        } else if (delta < -clampDelta) {
            *currentAngle = AngleMod(targetAngle + clampDelta);
        }
    }
}

#define BGS_ANIM_FLAGS_CHECK 0x30

/* RECOVERED(UO-GAME-UNK-0134): angle constants decoded from IEEE-754 bit patterns. */
#define BG_ANIM_TORSO_YAW_MAX_WIDE 90.0f
#define BG_ANIM_TORSO_YAW_MAX_TIGHT 45.0f
#define BG_ANIM_LEGS_YAW_DEADBAND 40.0f
#define BG_ANIM_LEGS_YAW_MAX 150.0f
#define BG_ANIM_LEAN_ANGLE_MAX 45.0f
#define BG_ANIM_LEAN_ANGLE_RATE 0.15f

long double GetLeanFraction(float fraction);
int BG_AllowPlayerWeaponAtVehiclePos(int vehicleType, int vehiclePos);

/* NOT_FROM_ORIGINAL_SOURCE: typed static animation entry accessor. */
static bg_static_animation_t *game_compat_bg_bgs_animation_entry(uint32_t animationWord)
{
    return &bgs.animationTable.entries[animationWord & ~ANIM_TOGGLEBIT];
}

/* NOT_FROM_ORIGINAL_SOURCE: typed static animation flags accessor. */
static uint32_t game_compat_bg_bgs_animation_flags(uint32_t animationWord)
{
    return game_compat_bg_bgs_animation_entry(animationWord)->flags;
}

/* NOT_FROM_ORIGINAL_SOURCE: typed static animation stateFlags accessor. */
static int32_t game_compat_bg_bgs_animation_state_group(uint32_t animationWord)
{
    return game_compat_bg_bgs_animation_entry(animationWord)->stateFlags;
}

/* 0x1dbac BG_PlayerAngles
 *
 * RECOVERED(UO-GAME-UNK-0134): Updates vehicle/lean animation swing angles.
 * Called from BG_PlayerAnimation with typed entity/client animation views. Slews torso yaw,
 * legs yaw, and lean angle toward targets based on vehicle state and flags.
 */
/* VERIFIED_DECOMPILER(0x1dbac, 2dbac_FUN_0002dbac.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - lean/view reads, vehicle animation-state fields, active-flag branches, torso/legs targets, override angle, swing-speed calls, bgs flags mask, vehicle-root overrides, pitch decay, and lean slew checked. */
static void BG_PlayerAngles(const gentity_t *entState, clientInfo_t *ci)
{
    const uint32_t entFlags = entState->s_flags;
    const uint32_t vehicleAnimState = (uint32_t)entState->eventParm;
    const int vehicleType = (vehicleAnimState & VEHICLE_ANIM_STATE_TYPE_MASK) >>
                            VEHICLE_ANIM_STATE_TYPE_SHIFT;
    const int vehiclePos = (vehicleAnimState & VEHICLE_ANIM_STATE_POS_MASK) >>
                           VEHICLE_ANIM_STATE_POS_SHIFT;
    const int animIndex = entState->entityStateAnim.playerAnim.legsAnim;

    float leanAmount = ci->leanAmount;
    float viewPitch = ci->viewPitch;
    float viewYaw = AngleMod(ci->viewYaw);
    float torsoTarget;
    float legsTarget;
    float torsoClamp;
    uint32_t conditionVal;

    (void)GetLeanFraction(ci->leanFraction);

    /* Determine if weapons are allowed at this vehicle position */
    const qboolean vehicleFlagsSet =
        (entFlags & BG_PS_FLAG_FIRE_POSITION_CHECK_MASK) != 0;
    const qboolean weaponAllowed = !vehicleFlagsSet ||
        BG_AllowPlayerWeaponAtVehiclePos(vehicleType, vehiclePos);

    /* Clear or set animation active flags based on conditions */
    if (weaponAllowed) {
        conditionVal = BG_GetConditionValue(ci, ANIM_COND_MOVETYPE, 0);
        if ((conditionVal & BG_ANIM_CLIMB_MOVE_TYPE_MASK) == 0) {
            conditionVal = BG_GetConditionValue(ci, ANIM_COND_MOVETYPE, 0);
            if ((conditionVal & 6) == 0) {
                ci->torsoYawActive = 1;
                ci->leanActive = 1;
                ci->legsYawActive = 1;
            } else {
                const int inputFlag = BG_GetConditionValue(ci, ANIM_COND_FIRING, 1);
                if (inputFlag != 0) {
                    ci->torsoYawActive = 1;
                    ci->leanActive = 1;
                }
            }
        } else {
            ci->torsoYawActive = 1;
            ci->leanActive = 1;
            ci->legsYawActive = 1;
        }
    } else {
        ci->torsoYawActive = 1;
        ci->leanActive = 1;
        ci->legsYawActive = 1;
    }

    /* Compute target angles */
    legsTarget = viewYaw + leanAmount;
    torsoTarget = viewYaw;

    if ((entFlags & 1) == 0) {
        /* Not dead/special state */
        conditionVal = BG_GetConditionValue(ci, ANIM_COND_MOVETYPE, 0);
        if ((conditionVal & BG_ANIM_CLIMB_MOVE_TYPE_MASK) == 0) {
            if ((entFlags & 0x40) == 0) {
                if ((entFlags & 0x200) == 0) {
                    if ((entFlags & 0x100) == 0) {
                        torsoTarget = viewYaw + leanAmount * 0.3f;
                    }
                    torsoClamp = BG_ANIM_TORSO_YAW_MAX_WIDE;
                } else {
                    torsoClamp = BG_ANIM_TORSO_YAW_MAX_TIGHT;
                }
            } else {
                torsoClamp = BG_ANIM_TORSO_YAW_MAX_WIDE;
            }
        } else {
            torsoClamp = 0.0f;
            torsoTarget = legsTarget;
        }
    } else {
        /* Dead or special state */
        torsoClamp = BG_ANIM_TORSO_YAW_MAX_WIDE;
        legsTarget = viewYaw;
    }

    /* Check for override angle from vehicle weapon position */
    if (vehicleFlagsSet &&
        BG_AllowPlayerWeaponAtVehiclePos(vehicleType, vehiclePos) &&
        entState->entityStateWeapon != 0) {
        legsTarget = ci->override.torsoState.overrideAngle;
    }

    /* Slew torso yaw */
    BG_SwingAngles(torsoTarget, 0.0f, torsoClamp,
                     bg_swingSpeed.value,
                     &ci->torsoYawAngle, &ci->torsoYawActive);

    /* Slew legs yaw */
    if ((entFlags & 1) == 0) {
        if ((entFlags & 0x40) == 0) {
            const uint32_t bgsAnimFlags = game_compat_bg_bgs_animation_flags((uint32_t)animIndex);
            if ((bgsAnimFlags & BGS_ANIM_FLAGS_CHECK) == 0) {
                if (!ci->legsYawActive) {
                    BG_SwingAngles(legsTarget, BG_ANIM_LEGS_YAW_DEADBAND,
                                     BG_ANIM_LEGS_YAW_MAX,
                                     bg_swingSpeed.value,
                                     &ci->legsYawAngle, &ci->legsYawActive);
                } else {
                    BG_SwingAngles(legsTarget, 0.0f, BG_ANIM_LEGS_YAW_MAX,
                                     bg_swingSpeed.value,
                                     &ci->legsYawAngle, &ci->legsYawActive);
                }
            } else {
                ci->legsYawActive = 0;
                BG_SwingAngles(viewYaw, 0.0f, BG_ANIM_LEGS_YAW_MAX,
                                 bg_swingSpeed.value,
                                 &ci->legsYawAngle, &ci->legsYawActive);
            }
        } else {
            ci->legsYawActive = 0;
            ci->legsYawAngle = viewYaw + leanAmount;
        }
    } else {
        BG_SwingAngles(legsTarget, 0.0f, BG_ANIM_LEGS_YAW_MAX,
                         bg_swingSpeed.value,
                         &ci->legsYawAngle, &ci->legsYawActive);
    }

    /* Override angles for vehicle state */
    if ((entFlags & BG_PS_FLAG_FIRE_POSITION_CHECK_MASK) == 0) {
        conditionVal = BG_GetConditionValue(ci, ANIM_COND_MOVETYPE, 0);
        if ((conditionVal & BG_ANIM_CLIMB_MOVE_TYPE_MASK) != 0) {
            ci->torsoYawAngle = viewYaw + leanAmount;
            ci->legsYawAngle = viewYaw + leanAmount;
        }
    } else {
        ci->torsoYawAngle = viewYaw;
        ci->legsYawAngle = viewYaw;
    }

    /* Handle lean angle decay */
    if ((entFlags & 1) == 0) {
        if ((entFlags & BG_PS_FLAG_FIRE_POSITION_CHECK_MASK) == 0) {
            conditionVal = BG_GetConditionValue(ci, ANIM_COND_MOVETYPE, 0);
            if ((conditionVal & BG_ANIM_CLIMB_MOVE_TYPE_MASK) == 0) {
                if (180.0f < viewPitch) {
                    viewPitch = (viewPitch - 360.0f) * 0.6f;
                } else {
                    viewPitch = viewPitch * 0.6f;
                }
            } else {
                viewPitch = 0.0f;
            }
        } else {
            viewPitch = 0.0f;
        }
    } else {
        viewPitch = 0.0f;
    }

    /* Slew lean angle */
    BG_SwingAngles(viewPitch, 0.0f, BG_ANIM_LEAN_ANGLE_MAX,
                     BG_ANIM_LEAN_ANGLE_RATE,
                     &ci->leanAngle, &ci->leanActive);

    /* VERIFIED_DECOMPILER(0x1dbac, 2dbac_FUN_0002dbac.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED -
     * transformed viewPitch remains local; original only updates leanAngle.
 */
}

/* 0x1e194 FUN_0002e194
 *
 * RECOVERED(UO-GAME-UNK-0134): Updates animation conditions from entity state.
 * Called from BG_PlayerAnimation with (ent, clientInfo). Similar to
 * BG_AnimUpdatePlayerStateConditions but reads from entity fields directly.
 */
/* VERIFIED_DECOMPILER(0x1e194, 2e194_FUN_0002e194.c, VERIFY-WAVE2-ANIMATION-STANCE-2026-06-17): DATAFLOW_VERIFIED
 * Evidence: matched entity condition writes, vehicle animation-state bitfields,
 * client-info pitch read, bgs animation stateFlags/flags, and unknown type drop.
 */
static void BG_AnimPlayerConditions(const gentity_t *entState,
                                    clientInfo_t *ci)
{
    const int clientNum = entState->clientNum;
    const int currentWeapon = entState->entityStateWeapon;
    const uint32_t entFlags = entState->s_flags;
    const uint32_t vehicleAnimState = (uint32_t)entState->eventParm;
    const int vehiclePos = (vehicleAnimState & VEHICLE_ANIM_STATE_POS_MASK) >>
                           VEHICLE_ANIM_STATE_POS_SHIFT;
    const int vehicleType = (vehicleAnimState & VEHICLE_ANIM_STATE_TYPE_MASK) >>
                            VEHICLE_ANIM_STATE_TYPE_SHIFT;
    const bg_anim_vehicle_motion_t vehicleMotion =
        (bg_anim_vehicle_motion_t)(
            (vehicleAnimState & VEHICLE_ANIM_STATE_MOTION_MASK) >>
            VEHICLE_ANIM_STATE_MOTION_SHIFT);
    const int animIndex = entState->entityStateAnim.playerAnim.legsAnim;
    /* Update weapon and weapon type conditions */
    BG_UpdateConditionValue(clientNum, ANIM_COND_WEAPON, currentWeapon, 1);
    BG_UpdateConditionValue(clientNum, ANIM_COND_WEAPONCLASS,
                            ((const weaponInfo_t *)BG_GetInfoForWeapon(currentWeapon))->weaponClass, 1);

    /* Update condition 9 from entity flag 0x100 */
    BG_UpdateConditionValue(
        clientNum, ANIM_COND_WEAPON_POSITION,
        (entFlags & 0x100) != 0 ? ANIM_WEAPON_POSITION_ADS
                                : ANIM_WEAPON_POSITION_HIP,
        1);

    /* If not linked into a vehicle, clear vehicle conditions and skip to end. */
    if ((entFlags & BG_PS_FLAG_IN_VEHICLE) == 0) {
        BG_UpdateConditionValue(
            clientNum, ANIM_COND_MOUNTED, ANIM_MOUNT_UNUSED, 1);
        BG_UpdateConditionValue(
            clientNum, ANIM_COND_VEHICLE, ANIM_VEHICLE_UNUSED, 1);
        BG_UpdateConditionValue(
            clientNum, ANIM_COND_VEHICLE_MOTION,
            ANIM_VEHICLE_MOTION_UNUSED, 1);
        goto update_remaining;
    }

    /* Update vehicle position condition (condition 2) */
    if (vehiclePos == 1) {
        BG_UpdateConditionValue(clientNum, ANIM_COND_MOUNTED,
                                ANIM_MOUNT_VEHICLE_DRIVER, 1);
    } else if (vehiclePos == 2) {
        BG_UpdateConditionValue(clientNum, ANIM_COND_MOUNTED,
                                ANIM_MOUNT_VEHICLE_GUNNER, 1);
    } else if (vehiclePos >= 3 && vehiclePos <= 6) {
        BG_UpdateConditionValue(clientNum, ANIM_COND_MOUNTED, vehiclePos + 1, 1);
    }

    /* Update vehicle type condition (condition 3) */
    if (vehicleType == 2) {
        BG_UpdateConditionValue(clientNum, ANIM_COND_VEHICLE,
                                ANIM_VEHICLE_TANK, 1);
    } else if (vehicleType == 1) {
        BG_UpdateConditionValue(clientNum, ANIM_COND_VEHICLE,
                                ANIM_VEHICLE_JEEP, 1);
    } else if (vehicleType == 5) {
        BG_UpdateConditionValue(clientNum, ANIM_COND_VEHICLE,
                                ANIM_VEHICLE_FLAK88, 1);
    } else {
        Com_Error(ERROR_DROP, BG_ANIM_PLAYER_CONDITIONS_VEHICLE_TYPE_ERROR,
                  vehicleType);
    }

    /* Update vehicle pitch condition (condition 4) */
    BG_UpdateConditionValue(clientNum, ANIM_COND_VEHICLE_MOTION, vehicleMotion, 1);

update_remaining:
    /* Update view pitch positive condition (condition 6) */
    BG_UpdateConditionValue(clientNum, ANIM_COND_UNDERHAND,
                            ci->viewPitch > 0.0f, 1);

    /* Update condition 7 from entity flag 0x20 */
    BG_UpdateConditionValue(clientNum, ANIM_COND_CROUCHING,
                            (entFlags & 0x20) != 0, 1);

    /* Update condition 8 from entity flag 0x200 */
    BG_UpdateConditionValue(clientNum, ANIM_COND_FIRING,
                            (entFlags & 0x200) != 0, 1);

    /* Update anim group condition (condition 5) from the static animation table. */
    const int bgsAnimValue = game_compat_bg_bgs_animation_state_group((uint32_t)animIndex);
    if (bgsAnimValue != 0) {
        BG_UpdateConditionValue(clientNum, ANIM_COND_MOVETYPE, bgsAnimValue, 0);
    }

    /* Update condition 10 from static animation flags. */
    const uint32_t bgsAnimFlags = game_compat_bg_bgs_animation_flags((uint32_t)animIndex);
    if ((bgsAnimFlags & BG_ANIM_ENTRY_STRAFE_LEFT) != 0) {
        BG_UpdateConditionValue(
            clientNum, ANIM_COND_STRAFING, ANIM_STRAFE_LEFT, 1);
    } else if ((bgsAnimFlags & BG_ANIM_ENTRY_STRAFE_RIGHT) != 0) {
        BG_UpdateConditionValue(
            clientNum, ANIM_COND_STRAFING, ANIM_STRAFE_RIGHT, 1);
    } else {
        BG_UpdateConditionValue(
            clientNum, ANIM_COND_STRAFING, ANIM_STRAFE_NOT, 1);
    }
}

/* 0x1e6ec BG_GetSurfIndex
 *
 * RECOVERED(UO-GAME-UNK-0135): Returns model index value for DObj construction.
 * The i386 helper returns constant zero.  Player DObj construction uses model
 * name strings to fetch XModels, and the companion "negative model index" slot
 * is zero in the stock helper; do not replace this with G_ModelIndex, which
 * would add configstring/precache side effects the decompile does not show.
 */
/* VERIFIED_DECOMPILER(0x1e6ec, 2e6ec_FUN_0002e6ec.c, VERIFY-ANIM-PACKET-2026-06-17): DATAFLOW_VERIFIED - ignored model-name argument and constant-zero return checked. */
static int BG_GetSurfIndex(const char *modelName)
{
    (void)modelName;
    return 0;
}

/* 0x1e6f6 BG_GetXModel
 *
 * RECOVERED(UO-GAME-UNK-0135): Returns XModel pointer for DObj construction.
 */
/* VERIFIED_DECOMPILER(0x1e6f6, 2e6f6_FUN_0002e6f6.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - sole trap_XModelGet call and wrapper return register preservation checked against decompiler and objdump 0x1e6f6..0x1e718. */
static XModel *BG_GetXModel(const char *modelName)
{
    return trap_XModelGet(modelName);
}

/* 0x1e719 BG_DObjCreate
 *
 * RECOVERED(UO-GAME-UNK-0135): Creates DObj from models array.
 */
/* VERIFIED_DECOMPILER(0x1e719, 2e719_FUN_0002e719.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - trap_DObjCreate argument order, uint16 model count widening, entity number, and final zero flags checked. */
static void BG_DObjCreate(DObjModel *models, uint16_t modelCount,
                          XAnimTree *animTree, int entityNum)
{
    trap_DObjCreate(models, modelCount, animTree, entityNum, 0);
}

/* 0x1e761 BG_Player_DoControllersInternal
 *
 * The Mac game_mp.dll traceback symbol supplies the exact function name. The
 * Linux wrapper at 0x1f313..0x1f335 proves its five-argument call shape; the
 * first entity and DObj-context arguments are present but unused by this build.
 * RECOVERED(UO-GAME-UNK-0136): Calculates 6 controller tag angles for player model.
 * Called from BG_Player_DoControllers. Computes weapon/view positioning based on
 * lean, view angles, vehicle state, and animation angles.
 */
/* VERIFIED_DECOMPILER(0x1e761, 2e761_FUN_0002e761.c, VERIFY-WAVE2-ANIMATION-STANCE-2026-06-17): DATAFLOW_VERIFIED
 * Evidence: matched vehicle override selection, prone/non-prone controller
 * weights, lean offsets, view-angle deltas, and tag_origin outputs.
 */
static void BG_SinCos(float radians, float *sinOut, float *cosOut);

static void BG_Player_DoControllersInternal(gentity_t *unusedEnt,
                                            const gentity_t *entState,
                                            uint32_t *unusedPartBits,
                                            clientInfo_t *ci,
                                            float *outputAngles)
{
    const uint32_t entFlags = entState->s_flags;
    float leanFraction;

    float torsoControllerPitch, torsoControllerYaw, torsoControllerRoll;
    float tagOriginPitch, tagOriginYaw, tagOriginRoll;
    float tagOriginOffsetX, tagOriginOffsetY;
    float viewControllerPitch, viewControllerYaw, viewControllerRoll;

    (void)unusedEnt;
    (void)unusedPartBits;

    /* Initialize from clientInfo */
    tagOriginPitch = 0.0f;
    torsoControllerPitch = 0.0f;
    viewControllerPitch = ci->viewPitch;
    viewControllerYaw = ci->viewYaw;
    viewControllerRoll = ci->viewRoll;
    tagOriginYaw = ci->legsYawAngle;
    torsoControllerYaw = ci->torsoYawAngle;
    torsoControllerRoll = torsoControllerPitch;
    tagOriginRoll = tagOriginPitch;

    /* Vehicle-linked handling */
    if ((entFlags & BG_PS_FLAG_IN_VEHICLE) != 0) {
        qboolean allowVehicleWeapon = qfalse;

        tagOriginPitch = ci->override.angles.pitch;
        tagOriginYaw = ci->override.angles.yaw;
        tagOriginRoll = ci->override.angles.roll;

        /* Stock does not read eventParm or call the vehicle-position query
         * when the entity-state weapon field is zero. */
        if (entState->entityStateWeapon != 0) {
            const uint32_t vehicleAnimState = (uint32_t)entState->eventParm;
            const int vehicleType =
                (vehicleAnimState & VEHICLE_ANIM_STATE_TYPE_MASK) >>
                VEHICLE_ANIM_STATE_TYPE_SHIFT;
            const int vehiclePos =
                (vehicleAnimState & VEHICLE_ANIM_STATE_POS_MASK) >>
                VEHICLE_ANIM_STATE_POS_SHIFT;

            allowVehicleWeapon =
                BG_AllowPlayerWeaponAtVehiclePos(vehicleType, vehiclePos) != 0;
        }

        if (!allowVehicleWeapon) {
            torsoControllerPitch = tagOriginPitch;
            torsoControllerYaw = tagOriginYaw;
            torsoControllerRoll = tagOriginRoll;
            viewControllerPitch = ci->viewPitch;
            viewControllerYaw = ci->viewYaw;
            viewControllerRoll = ci->viewRoll;
        } else {
            torsoControllerPitch = ci->viewPitch;
            torsoControllerYaw = ci->viewYaw;
            torsoControllerRoll = ci->viewRoll;
            viewControllerPitch = ci->viewPitch;
            viewControllerYaw = ci->viewYaw;
            viewControllerRoll = ci->viewRoll;
        }

        float yawDiff = AngleSubtract(viewControllerYaw, torsoControllerYaw);
        if (yawDiff > 90.0f) {
            yawDiff = 180.0f - yawDiff;
        } else if (yawDiff < -90.0f) {
            yawDiff = -180.0f - yawDiff;
        }
        viewControllerYaw = AngleNormalize180(yawDiff + torsoControllerYaw);
    }

    /* Non-vehicle, non-prone lean handling */
    if ((entFlags & BG_PS_FLAG_IN_VEHICLE) == 0 &&
        (BG_GetConditionValue(ci, ANIM_COND_MOVETYPE, 0) &
         BG_ANIM_CLIMB_MOVE_TYPE_MASK) == 0) {
        torsoControllerPitch = ci->leanAngle;
        if ((entFlags & 0x40) != 0) {
            torsoControllerPitch = AngleNormalize180(torsoControllerPitch);
            if (torsoControllerPitch > 0.0f) {
                torsoControllerPitch = torsoControllerPitch * 0.5f;
            } else {
                torsoControllerPitch = torsoControllerPitch * 0.25f;
            }
        }
    }

    {
        vec3_t viewDelta = {
            viewControllerPitch,
            viewControllerYaw,
            viewControllerRoll
        };
        vec3_t torsoDelta = {
            torsoControllerPitch,
            torsoControllerYaw,
            torsoControllerRoll
        };
        const vec3_t tagOriginAngles = {
            tagOriginPitch,
            tagOriginYaw,
            tagOriginRoll
        };

        /*
         * VERIFIED_DECOMP: FUN_0002e761 normalizes both local angle triples in
         * place before producing controller outputs:
         *   AnglesSubtract(&local_ac, &local_9c, &local_ac);
         *   AnglesSubtract(&local_9c, &local_6c, &local_9c);
         */
        AnglesSubtract(viewDelta, torsoDelta, viewDelta);
        AnglesSubtract(torsoDelta, tagOriginAngles, torsoDelta);

        viewControllerPitch = viewDelta[0];
        viewControllerYaw = viewDelta[1];
        viewControllerRoll = viewDelta[2];
        torsoControllerPitch = torsoDelta[0];
        torsoControllerYaw = torsoDelta[1];
        torsoControllerRoll = torsoDelta[2];
    }

    if ((entFlags & BG_PS_FLAG_IN_VEHICLE) != 0) {
        tagOriginPitch = 0.0f;
        tagOriginYaw = 0.0f;
        tagOriginRoll = 0.0f;
    }

    tagOriginOffsetX = 0.0f;
    tagOriginOffsetY = 0.0f;

    /* Stock snapshots the third tag-origin output before GetLeanFraction and
     * all remaining calls; preserve that read lifetime and call ordering. */
    const float tagOriginOffsetZ = entState->entityAngles[0];
    leanFraction = GetLeanFraction(ci->leanFraction);

    /* Lean-based offsets.  Stock 0x1eaca: (leanFraction*50)*0.925 kept 80-bit,
     * one store -> shim.  tagOriginOffsetY = -leanFraction*2.5 is a single mul
     * (48-bit product fits 80-bit exactly) -> native. */
#if EMULATE_X87
    float leanOffset = x87f_store_f32(x87f_mul(
        x87f_mul(x87f_load_f32(leanFraction), x87f_load_f32(50.0f)),
        x87f_load_f32(0.925f)));
#else
    float leanOffset = leanFraction * 50.0f * 0.925f;
#endif
    torsoControllerRoll = leanOffset;
    viewControllerRoll = leanOffset;
    /* Stock adds the product to the initialized +0.0 local; that addition is
     * significant for the zero sign when leanFraction is +0.0f. */
    tagOriginOffsetY += -leanFraction * 2.5f;

    /* View angle delta for non-dead, non-vehicle */
    if ((entFlags & 1) == 0 && (entFlags & BG_PS_FLAG_IN_VEHICLE) == 0) {
        tagOriginYaw = AngleSubtract(tagOriginYaw, ci->viewYaw);
    }

    float controllerAngles[18];

    if ((entFlags & 0x40) == 0) {
        /* Non-prone */
        if (leanFraction != 0.0f) {
            if ((entFlags & 0x20) == 0) {
                if (leanFraction > 0.0f) {
                    torsoControllerRoll *= 0.8f;
                    viewControllerRoll *= 0.8f;
                } else {
                    torsoControllerRoll *= 1.25f;
                    viewControllerRoll *= 1.25f;
                }
            } else if (leanFraction > 0.0f) {
                torsoControllerRoll *= 1.5f;
                viewControllerRoll *= 1.5f;
            } else {
                torsoControllerRoll *= 1.25f;
                viewControllerRoll *= 1.25f;
            }
        }
        /* Stock 0x1ee3c: (leanFraction*50)*0.075 kept 80-bit, + tagOriginRoll,
         * one store -> shim. */
#if EMULATE_X87
        tagOriginRoll = x87f_store_f32(x87f_add(
            x87f_mul(x87f_mul(x87f_load_f32(leanFraction), x87f_load_f32(50.0f)),
                     x87f_load_f32(0.075f)),
            x87f_load_f32(tagOriginRoll)));
#else
        tagOriginRoll += leanFraction * 50.0f * 0.075f;
#endif

        controllerAngles[0] = torsoControllerPitch * 0.2f;
        controllerAngles[1] = torsoControllerYaw * 0.4f;
        controllerAngles[2] = torsoControllerRoll * 0.5f;

        float viewYaw = entState->entityAngles[1];
        float viewPitch = entState->entityAngles[2];
        if (viewYaw != 0.0f || viewPitch != 0.0f) {
            controllerAngles[0] += AngleSubtract(viewYaw, viewPitch);
        }

        controllerAngles[3] = torsoControllerPitch * 0.3f;
        controllerAngles[4] = torsoControllerYaw * 0.4f;
        controllerAngles[5] = torsoControllerRoll * 0.5f;
        controllerAngles[6] = torsoControllerPitch * 0.5f;
        controllerAngles[7] = torsoControllerYaw * 0.2f;
        controllerAngles[8] = torsoControllerRoll * -0.6f;
    } else {
        /* Prone */
        if (leanFraction != 0.0f) {
            viewControllerRoll *= 0.5f;
        }
        tagOriginPitch += entState->entityAngles[1];

        /* 0x1ebae..0x1ebc4: stock multiplies by a QWORD double PI
         * (0x400921fb54442d18) and divides by a QWORD double 180.0, not float32
         * constants; the mul/div chain stays 80-bit, rounds to float at the arg
         * store -> shim. */
#if EMULATE_X87
        float yawRad = x87f_store_f32(x87f_div(
            x87f_mul(x87f_load_f32(torsoControllerYaw),
                     x87f_load_f64(3.141592653589793)),
            x87f_load_f64(180.0)));
#else
        float yawRad =
            (torsoControllerYaw * 3.141592653589793) /
            180.0;
#endif
        float sinVal, cosVal;
        /* Stock 0x1ebc7 CALLS BG_SinCos (the fsincos helper); keep every use
         * routed through that single architecture-aware leaf. */
        BG_SinCos(yawRad, &sinVal, &cosVal);

        /* Stock 0x1ebcc..0x1ec44: the offset updates keep each RHS 80-bit and do
         * one store. */
#if EMULATE_X87
        tagOriginOffsetX = x87f_store_f32(x87f_add(
            x87f_mul(x87f_sub(x87f_load_f32(1.0f), x87f_load_f32(cosVal)),
                     x87f_load_f32(-24.0f)),
            x87f_load_f32(tagOriginOffsetX)));
        tagOriginOffsetY = x87f_store_f32(x87f_add(
            x87f_mul(x87f_load_f32(sinVal), x87f_load_f32(-12.0f)),
            x87f_load_f32(tagOriginOffsetY)));

        if (x87f_lt(x87f_load_f32(0.0f),
                    x87f_mul(x87f_load_f32(leanFraction), x87f_load_f32(sinVal)))) {
            tagOriginOffsetY = x87f_store_f32(x87f_add(
                x87f_mul(x87f_mul(x87f_sub(x87f_load_f32(1.0f), x87f_load_f32(cosVal)),
                                  x87f_neg(x87f_load_f32(leanFraction))),
                         x87f_load_f32(16.0f)),
                x87f_load_f32(tagOriginOffsetY)));
        }
#else
        tagOriginOffsetX += (1.0f - cosVal) * -24.0f;
        tagOriginOffsetY += sinVal * -12.0f;

        if (leanFraction * sinVal > 0.0f) {
            tagOriginOffsetY += (1.0f - cosVal) * -leanFraction * 16.0f;
        }
#endif

        controllerAngles[0] = 0.0f;
        controllerAngles[1] = torsoControllerRoll * -1.2f;
        controllerAngles[2] = torsoControllerRoll * 0.3f;

        float viewYaw = entState->entityAngles[1];
        float viewPitch = entState->entityAngles[2];
        if (viewYaw != 0.0f || viewPitch != 0.0f) {
            controllerAngles[0] += AngleSubtract(viewYaw, viewPitch);
        }

        controllerAngles[3] = 0.0f;
        /* Stock 0x1ecd3/0x1ed0e: (yaw*a) - (roll*b) kept 80-bit, one store -> shim. */
#if EMULATE_X87
        controllerAngles[4] = x87f_store_f32(x87f_sub(
            x87f_mul(x87f_load_f32(torsoControllerYaw), x87f_load_f32(0.1f)),
            x87f_mul(x87f_load_f32(torsoControllerRoll), x87f_load_f32(0.2f))));
#else
        controllerAngles[4] =
            torsoControllerYaw * 0.1f - torsoControllerRoll * 0.2f;
#endif
        controllerAngles[5] = torsoControllerRoll * 0.2f;
        controllerAngles[6] = torsoControllerPitch;
#if EMULATE_X87
        controllerAngles[7] = x87f_store_f32(x87f_sub(
            x87f_mul(x87f_load_f32(torsoControllerYaw), x87f_load_f32(0.8f)),
            x87f_mul(x87f_load_f32(torsoControllerRoll), x87f_load_f32(-1.0f))));
#else
        controllerAngles[7] =
            torsoControllerYaw * 0.8f - torsoControllerRoll * -1.0f;
#endif
        controllerAngles[8] = torsoControllerRoll * -0.2f;
    }

    controllerAngles[9] = viewControllerPitch * 0.3f;
    controllerAngles[10] = viewControllerYaw * 0.3f;
    controllerAngles[11] = 0.0f;
    controllerAngles[12] = viewControllerPitch * 0.7f;
    controllerAngles[13] = viewControllerYaw * 0.7f;
    controllerAngles[14] = viewControllerRoll * -0.3f;
    controllerAngles[15] = 0.0f;
    controllerAngles[16] = 0.0f;
    controllerAngles[17] = 0.0f;

    float viewYaw = entState->entityAngles[1];
    float viewPitch = entState->entityAngles[2];
    if (viewPitch != 0.0f || viewYaw != 0.0f) {
        /* DATAFLOW_VERIFIED: final view-angle delta is stored in local_5c[0xf]. */
        controllerAngles[15] = AngleSubtract(viewPitch, viewYaw);
    }

    /* Write output */
    for (int i = 0; i < 6; i++) {
        outputAngles[i * 3] = controllerAngles[i * 3];
        outputAngles[i * 3 + 1] = controllerAngles[i * 3 + 1];
        outputAngles[i * 3 + 2] = controllerAngles[i * 3 + 2];
    }

    outputAngles[18] = tagOriginPitch;
    outputAngles[19] = tagOriginYaw;
    outputAngles[20] = tagOriginRoll;
    outputAngles[21] = tagOriginOffsetX;
    outputAngles[22] = tagOriginOffsetY;
    outputAngles[23] = tagOriginOffsetZ;
}

/* 0x1f107 FUN_0002f107
 *
 * Lerps a 3-component vector toward a target, clamping the step size.
 * Called from BG_Player_DoControllers to smoothly transition controller angles.
 */
/* VERIFIED_DECOMPILER(0x1f107, 2f107_FUN_0002f107.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - 3-element loop, positive/negative max-step clamps, exact target copy fallback, and void return checked. */
static void BG_LerpAngles(const float *target, float maxStep, float *current)
{
    for (int i = 0; i < 3; i++) {
        float delta = target[i] - current[i];
        if (maxStep < delta) {
            current[i] = current[i] + maxStep;
        } else if (delta < -maxStep) {
            current[i] = current[i] - maxStep;
        } else {
            current[i] = target[i];
        }
    }
}

float Q_rsqrt(float value);

/* NOT_FROM_ORIGINAL_SOURCE: emulates the stock i386 BG_LerpOffset call-site ABI.
 * ORIGINAL_BINARY_BUG: the original passes a double to Q_rsqrt's PLT slot,
 * while Q_rsqrt reads a float argument and leaves that float's raw return bits
 * in EAX; the caller converts EAX as a signed integer with fild before
 * multiplying by scale.  Preserve this malformed call/return dataflow; see
 * original-bugs/game-bg-lerp-offset-qrsqrt-abi.md. */
static float game_compat_bg_lerp_offset_original_qrsqrt_scale(float lengthSq, float scale)
{
    const double lengthSqDouble = (double)lengthSq;
    uint32_t lowWord;
    float qrsqrtArgument;
    float qrsqrtResult;
    int32_t returnWord;

    memcpy(&lowWord, &lengthSqDouble, sizeof(lowWord));
    memcpy(&qrsqrtArgument, &lowWord, sizeof(qrsqrtArgument));
    qrsqrtResult = Q_rsqrt(qrsqrtArgument);
    memcpy(&returnWord, &qrsqrtResult, sizeof(returnWord));

    /* 0x1f269: fild feeds the multiply with the EXACT integer value; a
     * (float)returnWord cast would round the >2^24 bit pattern first.
     * int32 -> double is exact -> shim (fild * scale, one store). */
#if EMULATE_X87
    return x87f_store_f32(
        x87f_mul(x87f_load_i32(returnWord), x87f_load_f32(scale)));
#else
    return scale * (double)returnWord;
#endif
}

/* 0x1f1e7 BG_LerpOffset
 *
 * Lerps a 3-component vector toward a target. The stock i386 call site uses a
 * mismatched Q_rsqrt ABI, so the step scale preserves that dataflow exactly.
 */
/* VERIFIED_DECOMPILER(0x1f1e7, 2f1e7_BG_LerpOffset.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - delta vector, length-squared zero guard, original Q_rsqrt double-argument/EAX-return ABI, step < 1 branch, incremental stores, and snap stores checked. */
void BG_LerpOffset(const float *target, float scale, float *current)
{
    float dx = target[0] - current[0];
    float dy = target[1] - current[1];
    float dz = target[2] - current[2];
    /* Stock 0x1f22c..0x1f242: 3-mul/2-add lengthSq kept 80-bit, one store -> shim. */
#if EMULATE_X87
    float lengthSq = x87f_store_f32(x87f_add(x87f_add(
        x87f_mul(x87f_load_f32(dx), x87f_load_f32(dx)),
        x87f_mul(x87f_load_f32(dy), x87f_load_f32(dy))),
        x87f_mul(x87f_load_f32(dz), x87f_load_f32(dz))));
#else
    float lengthSq = dx * dx + dy * dy + dz * dz;
#endif

    if (lengthSq != 0.0f) {
        float step = game_compat_bg_lerp_offset_original_qrsqrt_scale(lengthSq, scale);
        if (step < 1.0f) {
            /* Stock 0x1f290..: current[k] + d[k]*step kept 80-bit, one store. */
#if EMULATE_X87
            current[0] = x87f_store_f32(x87f_add(
                x87f_mul(x87f_load_f32(dx), x87f_load_f32(step)),
                x87f_load_f32(current[0])));
            current[1] = x87f_store_f32(x87f_add(
                x87f_mul(x87f_load_f32(dy), x87f_load_f32(step)),
                x87f_load_f32(current[1])));
            current[2] = x87f_store_f32(x87f_add(
                x87f_mul(x87f_load_f32(dz), x87f_load_f32(step)),
                x87f_load_f32(current[2])));
#else
            current[0] = current[0] + dx * step;
            current[1] = current[1] + dy * step;
            current[2] = current[2] + dz * step;
#endif
        } else {
            current[0] = target[0];
            current[1] = target[1];
            current[2] = target[2];
        }
    }
}

/* RECOVERED(UO-GAME-UNK-0136): controller tag count and offsets. */
#define BG_CONTROLLER_TAG_COUNT 6

const char *BG_ControllerTagNames[BG_CONTROLLER_TAG_COUNT] = {
    "back_low",
    "back_mid",
    "back_up",
    "neck",
    "head",
    "pelvis"
};
qboolean G_DObjSetControlTagAngles(gentity_t *ent, uint32_t *partBits,
                                          const char *tagName,
                                          const float *angles);
qboolean G_DObjSetLocalTag(gentity_t *ent, uint32_t *partBits,
                                  const char *tagName, const float *offset,
                                  const float *angles);

/* 0x1f2fe BG_Player_DoControllers
 *
 * Orchestrates controller tag angle calculation and application. Computes 6
 * controller angles, lerps them toward targets, and applies them to the DObj.
 * Also handles local tag offset for tag_origin.
 */
/* VERIFIED_DECOMPILER(0x1f2fe, 2f2fe_BG_Player_DoControllers.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - calculate call arguments, 0.36 frame-time scale, six 12-byte controller slots, tag-name table indexing, local tag angles at +0x450, offset at +0x45c, 0.1 frame-time offset scale, and tag_origin call order checked. */
void BG_Player_DoControllers(gentity_t *ent,
                             const gentity_t *entState,
                             uint32_t *partBits,
                             clientInfo_t *ciAnim)
{
    float controllerOutput[BG_CONTROLLER_TAG_COUNT * 3 + 6];

    /* Calculate target controller angles. */
    BG_Player_DoControllersInternal(ent, entState, partBits, ciAnim,
                                    controllerOutput);

    /* Stock snapshots frameTime after the internal controller calculation. */
    float timeScale = (float)bg.frameTime * 0.36f;

    /* Lerp and apply each controller tag */
    for (int i = 0; i < BG_CONTROLLER_TAG_COUNT; i++) {
        float *target = &controllerOutput[i * 3];
        float *current = ciAnim->controllerAngles[i];
        BG_LerpAngles(target, timeScale, current);
        G_DObjSetControlTagAngles(ent, partBits, BG_ControllerTagNames[i],
                                  current);
    }

    /* Lerp and apply local tag */
    float *localTargetAngles = &controllerOutput[18];
    float *localCurrentAngles = ciAnim->localTagAngles;
    BG_LerpAngles(localTargetAngles, timeScale, localCurrentAngles);

    float *offsetTarget = &controllerOutput[21];
    float *offsetCurrent = ciAnim->localTagOffset;
    BG_LerpOffset(offsetTarget, (float)bg.frameTime * 0.1f, offsetCurrent);

    G_DObjSetLocalTag(ent, partBits, "tag_origin", offsetCurrent,
                      localCurrentAngles);
}

/* NOT_FROM_ORIGINAL_SOURCE: slot identity helper extracted from 0x1ce5f. */
/* VERIFIED_DECOMPILER(0x1ce5f, 2ce5f_FUN_0002ce5f.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against original slot pointer comparison with ci + 0x380. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_SetNewAnimation (0x1ce5f); no standalone original body. */
static qboolean game_compat_bg_anim_slot_is_legs(const clientInfo_t *ci,
                                  const bg_anim_slot_t *slot)
{
    return slot == (const bg_anim_slot_t *)&ci->legsYawAngle;
}

/* NOT_FROM_ORIGINAL_SOURCE: slot XAnim word helper extracted from 0x1ce5f/0x1d56d. */
/* VERIFIED_DECOMPILER(0x1ce5f, 2ce5f_FUN_0002ce5f.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against CONCAT22(animTreeIndex, animWord low16) and 0xfffffdff toggle-bit mask. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_SetNewAnimation (0x1ce5f); no standalone original body. */
static uint32_t game_compat_bg_anim_slot_xanim(uint16_t animTreeIndex, uint32_t animationWord)
{
    return game_compat_bg_make_xanim(animTreeIndex, (uint16_t)animationWord) &
           ~ANIM_TOGGLEBIT;
}

/* NOT_FROM_ORIGINAL_SOURCE: last-origin copy helper extracted from 0x1d56d. */
/* VERIFIED_DECOMPILER(0x1d56d, 2d56d_FUN_0002d56d.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - source-only helper checked against three stores from fourth argument +0x18/+0x1c/+0x20, i.e. ent->pos.trBase. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_RunLerpFrameRate (0x1d56d); no standalone original body. */
static void game_compat_bg_copy_anim_slot_origin(bg_anim_slot_t *slot,
                                  const gentity_t *entState)
{
    slot->lastOrigin[0] = entState->pos.trBase[0];
    slot->lastOrigin[1] = entState->pos.trBase[1];
    slot->lastOrigin[2] = entState->pos.trBase[2];
}

/* NOT_FROM_ORIGINAL_SOURCE: loop phase helper extracted from 0x1ce5f. */
/* VERIFIED_DECOMPILER(0x1ce5f, 2ce5f_FUN_0002ce5f.c, VERIFY-ANIM-CONTROLLERS-2026-06-23): DATAFLOW_VERIFIED - source-only helper checked against clientNum * 0.36 plus bg.time % duration / duration minus x87 fistp RC=truncate integral phase. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original BG_SetNewAnimation (0x1ce5f); no standalone original body. */
static float game_compat_bg_initial_loop_time(const clientInfo_t *ci, int duration)
{
    /* 0x1d15e..0x1d17a: one 80-bit x87 chain with a single float rounding at
     * the phase store; separate framePhase/cyclePhase temporaries would round
     * three times. All operands feed the chain via fild (int->80, exact). */
#if EMULATE_X87
    const float phase = x87f_store_f32(x87f_add(
        x87f_mul(x87f_load_i32(ci->clientNum), x87f_load_f32(0.36f)),
        x87f_div(x87f_load_i32(bg.time % duration), x87f_load_i32(duration))));
    /* 0x1d19c..0x1d1a8: phase - (float)(int)phase kept 80-bit (fistp-trunc,
     * fild back, subtract), one store. */
    return x87f_store_f32(
        x87f_sub(x87f_load_f32(phase), x87f_load_i32((int)phase)));
#elif defined(__x86_64__)
    const float phase = (float)ci->clientNum * 0.36f +
                        (float)(bg.time % duration) / (float)duration;
    const int phaseInteger =
        CODUO_X87_TRUNCATE_I32((long double)phase);

    return phase - (float)phaseInteger;
#else
    const float phase = (float)ci->clientNum * 0.36f +
                        (float)(bg.time % duration) / (float)duration;

    return phase - (float)(int)phase;
#endif
}

/* 0x1ce5f BG_SetNewAnimation */
/* VERIFIED_DECOMPILER(0x1ce5f, 2ce5f_FUN_0002ce5f.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - old slot state capture, legs-slot identity, masked index/range error, slot pointer/offset cache, blend minima, crouch/prone transition timer, moving loop start phase, old-weight clear, zero-animation torso blend handles, torso dirty state, death/non-death weight paths, blend-handle weights, and debug print checked. */
static void BG_SetNewAnimation(clientInfo_t *ci, bg_anim_slot_t *slot,
                               uint32_t animationWord,
                               qboolean forceDeathRestart)
{
    bg_static_animation_t *oldAnimation = game_compat_bg_anim_slot_animation(slot);
    const uint32_t oldAnimationWord = slot->animationWord;
    const qboolean isLegsSlot = game_compat_bg_anim_slot_is_legs(ci, slot);
    const uint32_t animationIndex = animationWord & ~ANIM_TOGGLEBIT;
    float startTime = 0.0f;

    slot->animationWord = animationWord;

    if ((uint32_t)bgAnimStaticTable->count <= animationIndex) {
        Com_Error(ERROR_DROP, BG_UPDATE_ANIM_SLOT_RANGE_ERROR,
                  bgAnimStaticTable->count, animationIndex);
    }

    XAnimTree *animTree = ci->animTree;
    XAnim *anims = bgAnimStaticTable->anims;
    const uint16_t animTreeIndex = Scr_GetAnimsIndex(anims);
    if (animationIndex == 0) {
        game_compat_bg_set_anim_slot_animation(slot, NULL);
        slot->blendTime = BG_ANIM_NO_ANIMATION_BLEND_TIME_MS;
    } else {
        bg_static_animation_t *newAnimation = &bgAnimStaticTable->entries[animationIndex];

        game_compat_bg_set_anim_slot_animation(slot, newAnimation);
        slot->blendTime = newAnimation->blendTime;

        if (isLegsSlot &&
            (BG_IsCrouchingAnim(ci, animationIndex) !=
                 BG_IsCrouchingAnim(ci, oldAnimationWord) ||
             BG_IsProneAnim(ci, animationIndex) !=
                 BG_IsProneAnim(ci, oldAnimationWord))) {
            ci->animTransitionTime = bg.time + BG_ANIM_TRANSITION_PAD_MS;
        }
    }

    if (oldAnimation == NULL && isLegsSlot) {
        slot->blendTime = 0;
    } else {
        int minimumBlendTime = -1;

        bg_static_animation_t *currentAnimation = game_compat_bg_anim_slot_animation(slot);

        if (currentAnimation == NULL || slot->blendTime < 1) {
            if (currentAnimation == NULL || currentAnimation->moveSpeed == 0) {
                minimumBlendTime =
                    (oldAnimation == NULL || oldAnimation->moveSpeed == 0) ?
                        BG_ANIM_BLEND_IDLE_MS :
                        BG_ANIM_BLEND_MOVING_OUT_MS;
            } else {
                minimumBlendTime = BG_ANIM_BLEND_MOVING_IN_MS;
            }
        }

        const int transitionBlendTime = ci->animTransitionTime - bg.time;
        if (minimumBlendTime < transitionBlendTime) {
            minimumBlendTime = transitionBlendTime;
        }

        if (slot->blendTime < minimumBlendTime) {
            slot->blendTime = minimumBlendTime;
        }
    }

    bg_static_animation_t *currentAnimation = game_compat_bg_anim_slot_animation(slot);

    if (currentAnimation != NULL && currentAnimation->moveSpeed != 0) {
        const uint32_t newAnim = game_compat_bg_anim_slot_xanim(animTreeIndex, animationWord);

        if (trap_XAnimIsLooped(newAnim)) {
            const uint32_t oldAnim = game_compat_bg_anim_slot_xanim(animTreeIndex, oldAnimationWord);

            if (oldAnimation == NULL || oldAnimation->moveSpeed == 0 ||
                !trap_XAnimIsLooped(oldAnim)) {
                int duration = BG_ANIM_DEFAULT_CYCLE_MS;

                if (trap_XAnimIsPrimitive(oldAnim)) {
                    duration = trap_XAnimGetLength(anims,
                                                   (uint16_t)(oldAnimationWord &
                                                              ~ANIM_TOGGLEBIT)) +
                               BG_ANIM_LENGTH_PAD_MS;
                }

                startTime = game_compat_bg_initial_loop_time(ci, duration);
            } else {
                startTime = trap_XAnimGetTime(animTree, oldAnim);
            }
        }
    }

    if (oldAnimation != NULL) {
        const uint32_t oldAnim = game_compat_bg_anim_slot_xanim(animTreeIndex, oldAnimationWord);

        trap_XAnimClearGoalWeight(animTree, oldAnim,
                                  (float)slot->blendTime *
                                      BG_ANIM_MILLISECONDS_TO_SECONDS);
    }

    if (animationIndex == 0) {
        if (!isLegsSlot) {
            float blendSeconds =
                (float)slot->blendTime * BG_ANIM_MILLISECONDS_TO_SECONDS;

            trap_XAnimSetCompleteGoalWeight(
                animTree, bgs.resolvedTorsoAnimHandle, 0.0f, blendSeconds,
                1.0f, 0, 0);
            blendSeconds =
                (float)slot->blendTime * BG_ANIM_MILLISECONDS_TO_SECONDS;
            trap_XAnimSetCompleteGoalWeight(
                animTree, bgs.resolvedLegsAnimHandle, 1.0f, blendSeconds,
                1.0f, 0, 0);
        }

        /* Stock falls through to the common debug print even for index zero. */
        if (bg_debugAnim.integer == 1) {
            Com_Printf("Anim-%s: %i, %s, (blend time) %i\n",
                       isLegsSlot ? "legs " : "torso", animationIndex,
                       bgAnimStaticTable->entries[animationIndex].name,
                       slot->blendTime);
        }
        return;
    }

    if (!isLegsSlot) {
        ci->torsoTimer = 0;
        ci->dobjNeedsUpdate = 1;
    }

    const uint32_t newAnim = game_compat_bg_anim_slot_xanim(animTreeIndex, animationWord);

    currentAnimation = game_compat_bg_anim_slot_animation(slot);

    if ((currentAnimation->flags & BG_ANIM_ENTRY_DEATH) == 0) {
        const qboolean setStartTime =
            currentAnimation->moveSpeed != 0 &&
            trap_XAnimGetWeight(animTree, newAnim) == 0.0f;
        const float blendSeconds =
            (float)slot->blendTime * BG_ANIM_MILLISECONDS_TO_SECONDS;

        trap_XAnimSetCompleteGoalWeight(animTree, newAnim, 1.0f, blendSeconds, 1.0f,
                                        (uint16_t)currentAnimation->usedByScript,
                                        !isLegsSlot);
        if (setStartTime) {
            trap_XAnimSetTime(animTree, newAnim, startTime);
        }
    } else {
        if (trap_XAnimIsLooped(newAnim)) {
            Com_Error(ERROR_DROP, "death animation '%s' is looping",
                      currentAnimation->name);
        }

        if (!forceDeathRestart) {
            trap_XAnimSetCompleteGoalWeightKnobAll(
                animTree, newAnim, bgs.rootAnimHandle, 1.0f,
                0.0f, 1.0f, 0, 0);
            trap_XAnimSetTime(animTree, newAnim, 1.0f);
        } else {
            const float blendSeconds =
                (float)slot->blendTime * BG_ANIM_MILLISECONDS_TO_SECONDS;
            trap_XAnimSetCompleteGoalWeight(animTree, newAnim, 1.0f, blendSeconds,
                                            1.0f, 0, 0);
        }
    }

    if (!isLegsSlot) {
        float blendSeconds =
            (float)slot->blendTime * BG_ANIM_MILLISECONDS_TO_SECONDS;
        trap_XAnimSetCompleteGoalWeight(
            animTree, bgs.resolvedTorsoAnimHandle, 1.0f, blendSeconds, 1.0f,
            (uint16_t)currentAnimation->usedByScript, 0);
        blendSeconds =
            (float)slot->blendTime * BG_ANIM_MILLISECONDS_TO_SECONDS;
        trap_XAnimSetCompleteGoalWeight(
            animTree, bgs.resolvedLegsAnimHandle, 0.01f, blendSeconds,
            1.0f, (uint16_t)currentAnimation->usedByScript, 0);
    }

    if (bg_debugAnim.integer == 1) {
        Com_Printf("Anim-%s: %i, %s, (blend time) %i\n",
                   isLegsSlot ? "legs " : "torso", animationIndex,
                   currentAnimation->name, slot->blendTime);
    }
}

/* 0x1d56d BG_RunLerpFrameRate */
/* VERIFIED_DECOMPILER(0x1d56d, 2d56d_FUN_0002d56d.c, VERIFY-ANIM-CONTROLLERS-2026-06-17): DATAFLOW_VERIFIED - old-slot vertical-motion flag capture, anim tree/index lookup, update-slot gate and death-restart flag, masked zero return, reset path, level-frame delta guard, vertical vs VectorDistance movement, animRate calculation, min/zero/fast caps, debug print arguments, and trap_XAnimSetAnimRate checked. */
static void BG_RunLerpFrameRate(clientInfo_t *ci, bg_anim_slot_t *slot,
                               uint32_t animationWord,
                               const gentity_t *entState)
{
    bg_static_animation_t *slotAnimation = game_compat_bg_anim_slot_animation(slot);
    const qboolean usesVerticalDelta =
        slotAnimation != NULL &&
        (slotAnimation->flags & BG_ANIM_ENTRY_VERTICAL_MOTION) != 0;
    XAnimTree *animTree = ci->animTree;
    const uint16_t animTreeIndex =
        Scr_GetAnimsIndex(bgAnimStaticTable->anims);

    if (animationWord != slot->animationWord ||
        (game_compat_bg_anim_slot_animation(slot) == NULL &&
         (animationWord & ~ANIM_TOGGLEBIT) != 0)) {
        BG_SetNewAnimation(ci, slot, animationWord,
                          (entState->s_flags >> 10) & 1u);
    }

    if ((animationWord & ~ANIM_TOGGLEBIT) == 0) {
        return;
    }

    bg_static_animation_t *animation = game_compat_bg_anim_slot_animation(slot);
    if (animation->moveSpeed == 0 || slot->lastUpdateTime == 0) {
        slot->animRate = 1.0f;
        slot->lastUpdateTime = bg.levelFrameTime;
        game_compat_bg_copy_anim_slot_origin(slot, entState);
    } else if (bg.levelFrameTime != slot->lastUpdateTime) {
        float moveDistance;

        if (usesVerticalDelta) {
            moveDistance =
                fabsf(slot->lastOrigin[2] - entState->pos.trBase[2]);
        } else {
            moveDistance =
                VectorDistance(slot->lastOrigin, entState->pos.trBase);
        }

        /* 0x1d6c7..0x1d6db: the elapsed-seconds product (fild delta * SECONDS)
         * stays in the 80-bit chain; only the quotient is rounded to float. */
#if EMULATE_X87
        const float moveSpeed = x87f_store_f32(x87f_div(
            x87f_load_f32(moveDistance),
            x87f_mul(x87f_load_i32(bg.levelFrameTime - slot->lastUpdateTime),
                     x87f_load_f32(BG_ANIM_MILLISECONDS_TO_SECONDS))));
        /* 0x1d6e4..0x1d6ec: moveSpeed / (fild)animation->moveSpeed. */
        slot->animRate = x87f_store_f32(x87f_div(
            x87f_load_f32(moveSpeed), x87f_load_i32(animation->moveSpeed)));
#else
        const float moveSpeed =
            moveDistance /
            ((float)(bg.levelFrameTime - slot->lastUpdateTime) *
             BG_ANIM_MILLISECONDS_TO_SECONDS);

        slot->animRate = moveSpeed / (float)animation->moveSpeed;
#endif
        slot->lastUpdateTime = bg.levelFrameTime;
        game_compat_bg_copy_anim_slot_origin(slot, entState);

        if (slot->animRate < BG_ANIM_RATE_MIN) {
            if (slot->animRate >= BG_ANIM_RATE_ZERO_EPSILON ||
                !usesVerticalDelta) {
                slot->animRate = BG_ANIM_RATE_MIN;
            } else {
                slot->animRate = 0.0f;
            }
        } else if (slot->animRate > BG_ANIM_RATE_FAST_THRESHOLD) {
            if ((animation->flags & BG_ANIM_ENTRY_VERTICAL_MOTION) == 0) {
                if (animation->moveSpeed >= BG_ANIM_FAST_MOVE_SPEED_MIN) {
                    slot->animRate = BG_ANIM_RATE_CAP_FAST_MOVE;
                } else if (animation->moveSpeed < BG_ANIM_RATE_SPEED_BASE) {
                    if (slot->animRate > BG_ANIM_RATE_CAP_SLOW_MOVE) {
                        slot->animRate = BG_ANIM_RATE_CAP_SLOW_MOVE;
                    }
                } else {
                    const float maxRate =
                        BG_ANIM_RATE_CAP_SLOW_MOVE -
                        (float)(animation->moveSpeed -
                                BG_ANIM_RATE_SPEED_BASE) /
                            BG_ANIM_RATE_SPEED_RANGE;

                    if (slot->animRate > maxRate) {
                        slot->animRate = maxRate;
                    }
                }
            } else if (slot->animRate > BG_ANIM_RATE_CAP_VERTICAL_MOVE) {
                slot->animRate = BG_ANIM_RATE_CAP_VERTICAL_MOVE;
            }
        }

        if (bg_debugAnim.integer == 2) {
            Com_Printf("MoveSpeed: %s, %i, %4.4f : %1.4f\n",
                       bgAnimStaticTable->entries[animationWord &
                                             ~ANIM_TOGGLEBIT].name,
                       bgAnimStaticTable->entries[animationWord &
                                             ~ANIM_TOGGLEBIT].moveSpeed,
                       moveSpeed, slot->animRate);
        }
    }

    if (slot->animationWord != 0) {
        const uint32_t anim = game_compat_bg_anim_slot_xanim(animTreeIndex, slot->animationWord);

        trap_XAnimSetAnimRate(animTree, anim, slot->animRate);
    }
}

/* 0x1f462 BG_PlayerAnimation
 *
 * Main player animation update function. Called from G_RunClient. Orchestrates
 * vehicle swing angles, player anim conditions, slot cleanup, and movement rate.
 */
/* VERIFIED_DECOMPILER(0x1f462, 2f462_BG_PlayerAnimation.c, VERIFY-WAVE2-ANIMATION-STANCE-2026-06-17): DATAFLOW_VERIFIED
 * Evidence: matched call order, animTree slot cleanup, torso timer clear,
 * dobj dirty store, and legs/torso movement-rate updates from argument 2.
 */
void BG_PlayerAnimation(gentity_t *unusedEnt,
                        const gentity_t *entState,
                        clientInfo_t *ci)
{
    (void)unusedEnt;

    /* Update vehicle swing angles */
    BG_PlayerAngles(entState, ci);

    /* Update player animation conditions */
    BG_AnimPlayerConditions(entState, ci);

    /* Stock snapshots the tree only after both condition-update calls. */
    XAnimTree *animTree = ci->animTree;

    /* Clear animation slots if weight is zero */
    BG_PlayerAnimation_VerifyAnim(animTree,
        (bg_anim_slot_t *)&ci->legsYawAngle);
    BG_PlayerAnimation_VerifyAnim(animTree,
        (bg_anim_slot_t *)&ci->torsoYawAngle);

    /* Handle torso timer/animation state */
    if (ci->torsoTimer != 0 &&
        (ci->torsoAnimWord & ~ANIM_TOGGLEBIT) == 0) {
        ci->torsoTimer = 0;
        ci->dobjNeedsUpdate = 1;
    }

    /* Update movement rate for legs and torso */
    const uint32_t legsAnim =
        (uint32_t)entState->entityStateAnim.playerAnim.legsAnim;
    /* DATAFLOW_VERIFIED: original ignores argument 1 and uses argument 2 state. */
    BG_RunLerpFrameRate(ci, (bg_anim_slot_t *)&ci->legsYawAngle, legsAnim,
                          entState);
    /* Stock reads the torso word after the legs update returns. */
    const uint32_t torsoAnim =
        (uint32_t)entState->entityStateAnim.playerAnim.torsoAnim;
    BG_RunLerpFrameRate(ci, (bg_anim_slot_t *)&ci->torsoYawAngle, torsoAnim,
                          entState);
}

/* 0x1f556 BG_UpdatePlayerDObj
 *
 * Updates the player's DObj (Dynamic Object) model. Builds a DObjModel array
 * from clientInfo and creates/updates the DObj. Handles vehicle weapon state
 * and attached models.
 */
/* VERIFIED_DECOMPILER(0x1f556, 2f556_BG_UpdatePlayerDObj.c, VERIFY-WAVE2-ANIMATION-STANCE-2026-06-17): DATAFLOW_VERIFIED
 * Evidence: matched DObj existence/update gates, vehicle weapon suppression,
 * model/tag array population, ignore-collision bits, create call, and version bump.
 */
void BG_UpdatePlayerDObj(gentity_t *ent, const gentity_t *entState,
                         clientInfo_t *ci,
                         uint8_t *dObjVersion)
{
    qboolean dobjExists = trap_DObjExists(ent);
    int modelIndex = entState->entityStateWeapon;
    if ((entState->s_flags & BG_PS_FLAG_FIRE_POSITION_CHECK_MASK) != 0) {
        const uint32_t vehicleAnimState = (uint32_t)entState->eventParm;
        const int vehicleType =
            (vehicleAnimState & VEHICLE_ANIM_STATE_TYPE_MASK) >>
            VEHICLE_ANIM_STATE_TYPE_SHIFT;
        const int vehiclePos =
            (vehicleAnimState & VEHICLE_ANIM_STATE_POS_MASK) >>
            VEHICLE_ANIM_STATE_POS_SHIFT;

        if (BG_AllowPlayerWeaponAtVehiclePos(vehicleType, vehiclePos) == 0) {
            modelIndex = 0;
        }
    }

    if (ci->infoValid == 0 || ci->modelName[0] == '\0') {
        /* DATAFLOW_VERIFIED: original frees DObj for argument 2's entity number. */
        trap_SafeDObjFree(entState->s_number, 1);
        return;
    }

    if (dobjExists) {
        if (ci->dobjSavedModel == modelIndex &&
            ci->dobjNeedsUpdate == 0 &&
            ci->dobjVersion == *dObjVersion) {
            return;
        }
        trap_SafeDObjFree(entState->s_number, 0);
    }

    XAnimTree *animTree = ci->animTree;

    /* Build DObjModel array */
    DObjModel models[7];
    int modelCount = 0;

    /* Base model */
    /* FUN_0002e6ec is constant-zero in the i386 binary. */
    models[modelCount].negativeModelIndex =
        (int16_t)BG_GetSurfIndex(ci->modelName);
    models[modelCount].model = (XModel *)BG_GetXModel(ci->modelName);
    models[modelCount].tagName = NULL;
    models[modelCount].ignoreCollision = 0;
    modelCount++;

    /* Attached models */
    for (int slot = 0; slot < 6; slot++) {
        if (ci->attachModelNames[slot][0] == '\0') {
            continue;
        }

        /* FUN_0002e6ec is constant-zero in the i386 binary. */
        models[modelCount].negativeModelIndex =
            (int16_t)BG_GetSurfIndex(ci->attachModelNames[slot]);
        models[modelCount].model =
            (XModel *)BG_GetXModel(ci->attachModelNames[slot]);
        models[modelCount].tagName = ci->attachTagNames[slot];
        models[modelCount].ignoreCollision =
            (ent->attachIgnoreCollision >> slot) & 1;
        modelCount++;
    }

    BG_DObjCreate(models, (uint16_t)modelCount, animTree,
                        entState->s_number);

    ci->dobjSavedModel = modelIndex;
    if (ci->dobjNeedsUpdate != 0) {
        ci->dobjNeedsUpdate = 0;
        ci->dobjVersion++;
    }
    *dObjVersion = ci->dobjVersion;
}

/* 0x1f830 BG_FindAnims
 *
 * Initializes animation handles by calling Scr_FindAnim for key animations.
 * Called during game initialization to pre-load animation references.
 */
/* VERIFIED_DECOMPILER(0x1f830, 2f830_BG_FindAnims.c, VERIFY-ANIM-FIND-TREES-2026-06-17): DATAFLOW_VERIFIED - four Scr_FindAnim calls, multiplayer tree string, root/torso/legs/turning literal order, and loaded handle field offsets +0xa7ae4/+0xa7ae8/+0xa7aec/+0xa7af0 checked against decompiler and original disassembly. */
void BG_FindAnims(void)
{
    Scr_FindAnim("multiplayer", "root", &bgs.rootAnimHandle);
    Scr_FindAnim("multiplayer", "torso", &bgs.torsoAnimHandle);
    Scr_FindAnim("multiplayer", "legs", &bgs.legsAnimHandle);
    Scr_FindAnim("multiplayer", "turning", &bgs.turningAnimHandle);
}

script_anim_tree_ref_t Scr_FindAnimTree(const char *treeName);

/* 0x1f8e8 BG_FindAnimTree
 *
 * Looks up an animation tree by name, optionally erroring if not found.
 * Returns the output pointer for chaining.
 */
/* VERIFIED_DECOMPILER(0x1f8e8, 2f8e8_FUN_0002f8e8.c, VERIFY-ANIM-FIND-TREES-2026-06-17): DATAFLOW_VERIFIED - local tree temp, Scr_FindAnimTree argument order, missing-tree/drop branch, original error string, outTree store, and returned outTree pointer checked. */
static XAnim **BG_FindAnimTree(XAnim **outTree, const char *treeName,
                              qboolean errorIfMissing)
{
    XAnim *tree;
    tree = Scr_FindAnimTree(treeName).tree;
    if (tree == NULL && errorIfMissing != 0) {
        Com_Error(ERROR_DROP, BG_FIND_ANIM_TREE_ERROR, treeName);
    }
    *outTree = tree;
    return outTree;
}

/* 0x1f94a BG_FindAnimTrees
 *
 * Initializes animation tree instances. Finds the multiplayer anim tree
 * and copies instance pointers within the bgs structure.
 */
/* VERIFIED_DECOMPILER(0x1f94a, 2f94a_BG_FindAnimTrees.c, VERIFY-ANIM-FIND-TREES-2026-06-17): DATAFLOW_VERIFIED - multiplayer anims lookup with required-drop flag, multiplayerAnimTree destination, and anims/torso/legs/turning handle copy sources and destinations checked. */
void BG_FindAnimTrees(void)
{
    BG_FindAnimTree(&bgs.multiplayerAnimTree, "multiplayer", 1);

    bgs.animationTable.anims = bgs.multiplayerAnimTree;
    bgs.resolvedTorsoAnimHandle = bgs.torsoAnimHandle;
    bgs.resolvedLegsAnimHandle = bgs.legsAnimHandle;
    bgs.resolvedTurningAnimHandle = bgs.turningAnimHandle;
}

#define BGS_CLIENTINFO_COUNT 64

/* 0x1f9ea BG_LoadAnimTreeInstances
 *
 * Loads animation tree instances for all client slots. Creates XAnim trees
 * from the base anims pointer and stores them in per-client bgs slots.
 */
/* VERIFIED_DECOMPILER(0x1f9ea, 2f9ea_BG_LoadAnimTreeInstances.c, VERIFY-FIRST-FUNCTIONS-2026-06-17): DATAFLOW_VERIFIED; 64-slot clientinfo loop, base tree read, XAnim tree creation, and animTree store checked against current decompiler output. */
void BG_LoadAnimTreeInstances(void)
{
    clientInfo_t *clientinfo = bgs.clientinfo;
    XAnim *baseTree = bgs.multiplayerAnimTree;

    for (int i = 0; i < BGS_CLIENTINFO_COUNT; i++) {
        XAnimTree *treeInstance = trap_XAnimCreateTree(baseTree);
        clientinfo[i].animTree = treeInstance;
    }
}

/* 0x1fa4d FUN_0002fa4d
 *
 * Computes sine and cosine of an angle in radians.
 */
/* VERIFIED_DECOMPILER(0x1fa4d, 2fa4d_FUN_0002fa4d.c, VERIFY-ANIM-FIND-TREES-2026-06-17): DATAFLOW_VERIFIED - fsincos behavior, cosine-then-sine output store order, and void return checked against decompiler and original disassembly. */
/* Stock 0x1fa4d is `fld DWORD; fsincos; fstp(cos); fstp(sin)`.  Native x87
 * targets use that exact sequence; only non-x87 targets use the permitted libm
 * fallback.  Callers stay routed through this single transcendental leaf. */
static void BG_SinCos(float radians, float *sinOut, float *cosOut)
{
#if defined(__i386__) || defined(__x86_64__)
    coduo_x87_sincosf(radians, sinOut, cosOut);
#else
    float cosValue = cosf(radians);
    float sinValue = sinf(radians);

    *cosOut = cosValue;
    *sinOut = sinValue;
#endif
}
