/*
 * Source reconstruction for item pickup helpers.
 *
 * Binary SHA-256:
 * 4d2391c10e234559ace294fb69d96741cad522653c361f13f626f4dc1891acb7
 */

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

#include "recovered_game.h"
#include "game_types.h"
#include "game_globals.h"
#include "level_locals.h"
#include "scr_vm.h"
#include "g_syscalls.h"
#include "game_functions.h"
#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */
#include "coduo_native_x87.h"
#include "coduo_libm.h"

#if EMULATE_X87
/* 80-bit variant of GetLeanFraction (common_math.c) for callers that keep the
 * result in an x87 chain instead of rounding it to float immediately. */
x87f game_compat_get_lean_fraction_x87(float fraction);
#endif

#define DROPPED_WEAPON_SLOT_COUNT 32
#define DROPPED_WEAPON_MIN_SLOTS 1
#define ITEM_RESPAWN_PACKED_STATE (0x407c0000u | 0x00000008u)
#define ITEM_SPAWN_PACKED_STATE (0x407c0000u | 0x00000108u)
#define ITEM_SVF_RESPAWNING 0x00000001u
#define ITEM_DELAYED_SPAWN_MS 200
#define ITEM_WEAPON_ANGLE_ADJUST 90.0f
#define ITEM_DROP_TRACE_DISTANCE 4096.0f
#define ITEM_STARTSOLID_RETRY_Z 15.0f
#define ITEM_TRACE_CONTENTS 0x411
#define DROPPED_ITEM_CLIPMASK 0x81
#define DROPPED_ITEM_FLAGS 0x00000010u
#define DROPPED_ITEM_CLEAR_OWNER_MS 1000
#define DROPPED_ITEM_PICKUP_FREE_DELAY_MS 100
#define DROPPED_ITEM_THROW_SPEED 150.0f
#define DROPPED_ITEM_THROW_UP 200.0f
#define DROPPED_ITEM_THROW_UP_RANDOM 50.0f
#define DROPPED_WEAPON_TAG_YAW_RANDOM 50.0f
#define DROPPED_WEAPON_TAG_PITCH_RANDOM 40.0f
#define DROPPED_WEAPON_TAG_ROLL_RANDOM 60.0f
#define WEAPON_REDROP_TRACE_DISTANCE 256.0f
#define ITEM_DEFAULT_CLIPMASK 0x491
#define ITEM_POINT_CONTENTS_MASK 0x80000000u
#define ITEM_BOUNCE_RETRACE_Z 128.0f
#define ITEM_BOUNCE_STOP_Z 40.0f
#define ITEM_BOUNCE_SETTLE_MIN_Z 0.5f
#define ITEM_BOUNCE_SETTLE_RANDOM_Z 0.5f
#define RAND_FLOAT_DENOM 2147483648.0f /* 2^31; original float32 0x4f000000 */
#define PICKUP_AMMO_RESPAWN_TIME 40
#define PICKUP_HEALTH_NO_RESPAWN -1
#define PICKUP_HEALTH_SMALL 5
#define PICKUP_HEALTH_LARGE 100
#define ITEM_REGISTERED_COUNT 256
#define ITEM_CONFIGSTRING_REGISTERED 8
#define ITEM_REGISTERED_NIBBLE_BITS 4
#define ITEM_INDEX_MIN 1
#define ITEM_INDEX_MAX 133
#define ITEM_TOUCH_XY_RANGE 36.0f
#define ITEM_TOUCH_Z_ABOVE 18.0f
#define ITEM_TOUCH_Z_BELOW 88.0f
#define ITEM_TOUCH_MANUAL 0
#define ITEM_TOUCH_AUTOGRAB 2
#define PS_STANCE_FLAG_PRONE 0x00000001u
#define PS_STANCE_FLAG_MELEE_HELD 0x00001000u
#define PS_STANCE_FLAG_SWITCH_BLOCKED 0x00000010u
#define PS_STANCE_FLAG_KEEP_CURRENT_WEAPON 0x00004000u
#define PS_STANCE_FLAG_ADS 0x00000020u
#define PS_STANCE_FLAG_SPRINTING 0x00010000u
#define PS_STANCE_FLAG_WEAPON_DISABLED 0x00400000u
#define PS_FLAG_IN_VEHICLE 0x00100000u
#define PS_FLAG_VEHICLE_ALLOW_WEAPON 0x00400000u
#define PS_FLAG_WEAPON_ROT_CROUCH 0x00000020u
#define PS_FLAG_WEAPON_ROT_PRONE 0x00000040u
#define PLAYERSTATE_FLAG_WEAPON_ROT_SPRINT 0x00010000u
#define PS_FLAG_FIRE_POSITION_CHECK_MASK 0x00106000u
#define WEAPON_ANIM_TOGGLE_BIT 0x00000200u
#define WEAPON_ANIM_INDEX_MASK 0xfffffdffu
#define GCLIENT_OFFSET_WEAPON_SLOT_BASE 0x544
#define PM_AIM_SPREAD_SCALE_ALT_SWITCH_MIN 128.0f
#define PM_AIM_SPREAD_SCALE_MAX 255.0f
#define PM_WEAPON_ADS_RAISE_THRESHOLD 0.75f
#define PM_WEAPON_RELOAD_ADS_FRACTION_MIN 0.99f
#define PM_WEAPON_NO_AMMO_TIME_PENALTY 500
#define PMOVE_WEAPON_INPUT_PRIMARY 1u
#define PMOVE_WEAPON_INPUT_SPECIAL_FIRE 16u
#define PMOVE_WEAPON_INPUT_MELEE 32u
#define PMOVE_WEAPON_BUTTON_RELOAD 8u
#define WEAPON_IDLE_SWAY_DEFAULT 80.0f
#define WEAPON_IDLE_SWAY_SCALE_STEP 0.5f
#define WEAPON_IDLE_SWAY_PITCH_FREQ 0.001f
#define WEAPON_IDLE_SWAY_YAW_FREQ 0.0007f /* original float32 0x3a378034 */
#define WEAPON_IDLE_SWAY_ROLL_FREQ 0.0005f /* original float32 0x3a03126f */
#define WEAPON_IDLE_SWAY_PITCH_SCALE 0.01f
#define WEAPON_IDLE_SWAY_YAW_SCALE 0.01f
#define WEAPON_IDLE_SWAY_ROLL_SCALE 0.04f
#define WEAPON_BOB_CYCLE_MAX 255.0f
/* The bob/spread angle constants are float64 in the original module: every
 * load of these values is an fld QWORD (e.g. 0x37b56, 0x384dd, 0x37c19). */
#define WEAPON_BOB_PI 3.141592653589793 /* original float64 0x400921fb54442d18 */
#define WEAPON_BOB_TWO_PI 6.283185307179586 /* original float64 0x401921fb54442d18 */
#define WEAPON_BOB_HALF_PI 1.5707963267948966 /* original float64 0x3ff921fb54442d18 */
#define WEAPON_BOB_QUARTER_PI 0.7853981633974483 /* original float64 0x3fe921fb54442d18 */
#define WEAPON_BOB_ROLL_PHASE 0.47123889803846897 /* original float64 0x3fde28c7460698c7 */
#define WEAPON_BOB_SPEED_SCALE 0.16f
#define WEAPON_BOB_ROLL_SPEED_SCALE 1.5f
#define WEAPON_BOB_VERTICAL_HARMONIC 4.0f
#define WEAPON_BOB_VERTICAL_HARMONIC_SCALE 0.2f
#define WEAPON_BOB_VERTICAL_SCALE 0.75f
#define WEAPON_BOB_MAX_AMPLITUDE 10.0f
#define VIEW_BOB_MAX_AMPLITUDE 45.0f
#define WEAPON_VIEWKICK_ADS_BASE 0.5f
#define WEAPON_VIEWKICK_FADE_IN_MS 100.0f
#define WEAPON_VIEWKICK_FADE_OUT_MS 400.0f
#define WEAPON_VIEWKICK_ADS_REDUCTION 0.75f
#define WEAPON_VIEWKICK_PITCH_SCALE 0.5f
#define WEAPON_VIEWKICK_ROLL_SCALE 0.5f
#define VIEWKICK_ADS_SCALE 0.5f
#define VIEWKICK_FADE_IN_MS 100.0f
#define VIEWKICK_FADE_OUT_MS 400.0f
#define WEAPON_RECOIL_ANGLE_EPSILON 0.25f
#define WEAPON_RECOIL_VELOCITY_EPSILON 1.0f
#define WEAPON_RECOIL_STEP_TIME 0.005f
/* The recoil loop compares/decrements against float64 0.005 (fld QWORD at
 * 0x38c24) while the step value itself is the float32 immediate 0x3ba3d70a. */
#define WEAPON_RECOIL_STEP_TIME_F64 0.005 /* original float64 0x3f747ae147ae147b */
#define WEAPON_FIRE_RECOIL_VIEWKICK_ROLL_SCALE -0.5f
#define WEAPON_SWAY_SMOOTH_EPSILON 0.001 /* original float64 0x3f50624dd2f1a9fc */
#define WEAPON_SWAY_MSEC_TO_SEC 0.001f
#define BULLET_TRACE_DISTANCE 10000.0f
#define DEGREES_PER_HALF_CIRCLE 180.0 /* original float64 0x4066800000000000 (0x30140) */
#define PICKUP_EVENT_EXISTING_WEAPON_AMMO EV_AMMO_PICKUP
#define CLIENT_CLEAN_NAME_LEN 64
#define ERROR_DROP 1
#define WEAPON_FILE_BUFFER_SIZE 8192
#define WEAPON_FILE_PATH_SIZE 64
#define WEAPON_FILE_LIST_BUFFER_SIZE 4096
#define WEAPON_FILE_CONFIGSTRING_BUFFER_SIZE 8192
#define WEAPON_INFO_MAX 128
#define WEAPON_FILE_MAX 127
#define WEAPON_CONFIGSTRING_FILES 7
#define HINTSTRING_WEAPON_MAX 32
#define WEAPON_FILE_MAGIC "WEAPONFILE"
#define WEAPON_FILE_LIST_EXTENSION ""
#define WEAPON_AMMO_NONE_NAME "none"
#define WEAPON_ADS_IN_DEFAULT_RATE 0.0033333334f /* original float32 0x3b5a740e */
#define WEAPON_ADS_OUT_DEFAULT_RATE 0.0020000001f /* original float32 0x3b03126f */
#define WEAPON_PARSE_CUSTOM_TYPE_MAX 15
#define WEAPON_PARSE_FIELD_STRING PARSE_FIELD_STRING_ALLOC
#define WEAPON_PARSE_TYPE_WEAPON_TYPE 8
#define WEAPON_PARSE_TYPE_WEAPON_CLASS 9
#define WEAPON_PARSE_TYPE_AMMO_TYPE 10
#define WEAPON_PARSE_TYPE_OVERLAY_RETICLE 11
#define WEAPON_PARSE_TYPE_WEAPON_SLOT 12
#define WEAPON_PARSE_TYPE_STANCE 13
#define WEAPON_PARSE_TYPE_GRENADE_TYPE 14
#define WEAPON_OVERLAY_RETICLE_COUNT 5
#define WEAPON_STANCE_COUNT 3
#define WEAPON_GRENADE_TYPE_COUNT 9

const char *bg_ammoTypeNames[WEAPON_INFO_MAX];
int32_t bg_ammoTypeMax[WEAPON_INFO_MAX];
int32_t bg_numAmmoTypes;
const char *bg_sharedAmmoCapNames[WEAPON_INFO_MAX];
int32_t bg_sharedAmmoCapSizes[WEAPON_INFO_MAX];
int32_t bg_numSharedAmmoCaps;
const char *bg_ammoClipNames[WEAPON_INFO_MAX];
int32_t bg_ammoClipSizes[WEAPON_INFO_MAX];
int32_t bg_numAmmoClips;
int bg_numWeapons;
weaponInfo_t **bg_weaponInfos;
const char *bg_weaponEmptyString;
static const char *const bg_weaponTypeNames[] = {
    "bullet",
    "grenade",
    "projectile",
    "spotter",
    "gas",
}; /* PTR_s_bullet_000bb468 */
const char *const bg_weaponSlotNames[WEAPSLOT_COUNT] = {
    "none",
    "primary",
    "primaryb",
    "pistol",
    "grenade",
    "smokegrenade",
    "satchel",
    "binocular",
};
const char *bg_szWeaponsFolder = "weapons/mp";

#define BG_WEAPON_PARSE_FIELD(key, member, fieldType) \
    { key, (int32_t)offsetof(weaponInfo_t, member), fieldType }

static const parseField_t bg_weaponParseFields[] = {
    BG_WEAPON_PARSE_FIELD("displayName", displayName, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("AIOverlayDescription", aiOverlayDescription, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("modeName", modeName, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("gunModel", gunModel, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("handModel", handModel, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("idleAnim", idleAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("emptyIdleAnim", emptyIdleAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("fireAnim", fireAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("holdFireAnim", holdFireAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("lastShotAnim", lastShotAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("rechamberAnim", rechamberAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("meleeAnim", meleeAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reloadAnim", reloadAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reloadEmptyAnim", reloadEmptyAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reloadStartAnim", reloadStartAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reloadEndAnim", reloadEndAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("raiseAnim", raiseAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("dropAnim", dropAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("altRaiseAnim", altRaiseAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("altDropAnim", altDropAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("adsFireAnim", adsFireAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("adsLastShotAnim", adsLastShotAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("adsRechamberAnim", adsRechamberAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("lmgDeployedAnim", lmgDeployedAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("lmgDeployAnim", lmgDeployAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("lmgBreakdownAnim", lmgBreakdownAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("adsDeployedAnim", lmgDeployedAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("adsDeployAnim", lmgDeployAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("adsBreakdownAnim", lmgBreakdownAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("adsUpAnim", adsUpAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("adsDownAnim", adsDownAnim, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("script", script, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("weaponType", weaponType, WEAPON_PARSE_TYPE_WEAPON_TYPE),
    BG_WEAPON_PARSE_FIELD("weaponClass", weaponClass, WEAPON_PARSE_TYPE_WEAPON_CLASS),
    BG_WEAPON_PARSE_FIELD("weaponSlot", slot, WEAPON_PARSE_TYPE_WEAPON_SLOT),
    BG_WEAPON_PARSE_FIELD("slotStackable", stackable, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("ammoType", ammoType, WEAPON_PARSE_TYPE_AMMO_TYPE),
    BG_WEAPON_PARSE_FIELD("viewFlashEffect", viewFlashEffect, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("worldFlashEffect", worldFlashEffect, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("pickupSound", pickupSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("ammoPickupSound", ammoPickupSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("projectileSound", projectileSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("pullbackSound", pullbackSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("fireSound", fireSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("loopFireSound", loopFireSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("stopFireSound", stopFireSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("fireEchoSound", fireEchoSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("lastShotSound", lastShotSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("rechamberSound", rechamberSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reloadSound", reloadSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reloadEmptySound", reloadEmptySound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reloadStartSound", reloadStartSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reloadEndSound", reloadEndSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("raiseSound", raiseSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("altSwitchSound", altSwitchSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("putawaySound", putawaySound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("deploySound", deploySound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("breakdownSound", breakdownSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("noteTrackSoundA", noteTrackSoundA, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("noteTrackSoundB", noteTrackSoundB, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("noteTrackSoundC", noteTrackSoundC, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("noteTrackSoundD", noteTrackSoundD, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("projectileSoundBlend1", projectileSoundBlend1, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("projectileSoundBlend2", projectileSoundBlend2, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("shellEjectEffect", shellEjectEffect, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("lastShotEjectEffect", lastShotEjectEffect, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reticleCenter", reticleCenter, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reticleSide", reticleSide, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("reticleCenterSize", reticleCenterSize, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("reticleSideSize", reticleSideSize, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("reticleMinOfs", reticleMinOfs, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("sprintMoveF", sprintMove[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("sprintMoveR", sprintMove[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("sprintMoveU", sprintMove[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("sprintRotP", sprintRot[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("sprintRotY", sprintRot[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("sprintRotR", sprintRot[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("standMoveF", standMove[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("standMoveR", standMove[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("standMoveU", standMove[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("standRotP", standRot[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("standRotY", standRot[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("standRotR", standRot[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedOfsF", duckedOffset[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedOfsR", duckedOffset[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedOfsU", duckedOffset[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedMoveF", duckedMove[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedMoveR", duckedMove[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedMoveU", duckedMove[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedRotP", duckedRot[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedRotY", duckedRot[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedRotR", duckedRot[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneOfsF", proneOffset[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneOfsR", proneOffset[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneOfsU", proneOffset[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneMoveF", proneMove[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneMoveR", proneMove[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneMoveU", proneMove[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneRotP", proneRot[0], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneRotY", proneRot[1], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneRotR", proneRot[2], PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("posMoveRate", moveSmooth, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("posProneMoveRate", moveSmoothProne, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("sprintMoveMinSpeed", moveThresholdAlt, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("standMoveMinSpeed", moveThresholdStand, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedMoveMinSpeed", moveThresholdCrouch, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneMoveMinSpeed", moveThresholdProne, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("posRotRate", posRotRate, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("posProneRotRate", posProneRotRate, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("standRotMinSpeed", standRotMinSpeed, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("duckedRotMinSpeed", duckedRotMinSpeed, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("proneRotMinSpeed", proneRotMinSpeed, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("radiantName", scriptClassname, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("worldModel", worldModel, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("pickupModel", pickupModel, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("attachModel", viewModel, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("hudIcon", hudIcon, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("modeIcon", modeIcon, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("ammoIcon", ammoIcon, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("startAmmo", startAmmo, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("ammoName", ammoName, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("clipName", clipName, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("maxAmmo", maxAmmo, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("clipSize", clipSize, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("sharedAmmoCapName", sharedAmmoCapName, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("sharedAmmoCap", sharedAmmoCap, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("damage", flameDamage, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("meleeDamage", meleeDamage, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("damageInnerRadius", damageFalloffMinRange, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("damageOuterRadius", damageFalloffMaxRange, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("minDamagePercent", damageFalloffMinDamagePercent, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("fireDelay", fireDelay, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("meleeDelay", meleeWindup, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("fireTime", fireTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("rechamberTime", raiseTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("rechamberBoltTime", raiseInterruptTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("holdFireTime", specialFireDelay, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("meleeTime", meleeTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("reloadTime", reloadTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("reloadEmptyTime", reloadEmptyTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("reloadAddTime", reloadAddTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("reloadStartTime", reloadStartTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("reloadStartAddTime", reloadStartAddTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("reloadEndTime", reloadEndTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("dropTime", lowerTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("raiseTime", switchRaiseTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("altDropTime", altSwitchLowerTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("altRaiseTime", altSwitchRaiseTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("fuseTime", specialTimeThreshold, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("moveSpeedScale", moveSpeedScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("idleCrouchFactor", idleSwayCrouchScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("idleProneFactor", idleSwayProneScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("gunMaxPitch", gunMaxPitch, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("gunMaxYaw", gunMaxYaw, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("swayMaxAngle", swayMaxAngle, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("swayLerpSpeed", swayLerpSpeed, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("swayPitchScale", swayPitchScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("swayYawScale", swayYawScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("swayHorizScale", swayHorizScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("swayVertScale", swayVertScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("swayShellShockScale", swayShellShockScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSwayMaxAngle", adsSwayMaxAngle, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSwayLerpSpeed", adsSwayLerpSpeed, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSwayPitchScale", adsSwayPitchScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSwayYawScale", adsSwayYawScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSwayHorizScale", adsSwayHorizScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSwayVertScale", adsSwayVertScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("rifleBullet", ricochet, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("twoHanded", twoHanded, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("semiAuto", weaponTimeHold, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("boltAction", raiseEnabled, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("aimDownSight", adsEnabled, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("rechamberWhileAds", adsRaiseEnabled, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("adsViewErrorMin", adsViewErrorMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsViewErrorMax", adsViewErrorMax, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("clipOnly", clipRequired, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("cookOffHold", specialTimeEnabled, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("cloth", cloth, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("wideListIcon", wideListIcon, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("adsFire", adsFireDelayEnabled, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("doNotDrop", dropDisabled, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("killIcon", killIcon, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("wideKillIcon", wideKillIcon, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("noPartialReload", reloadRequiresAddRoom, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("segmentedReload", segmentedReload, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("reloadAmmoAdd", reloadAmmoAdd, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("reloadStartAdd", reloadStartAmmoAdd, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("altWeapon", altWeaponName, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("dropAmmoMin", dropAmmoMin, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("dropAmmoMax", dropAmmoMax, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("takedamage", grenadeTouchDamageEnabled, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("vehiclescale", grenadeSplashVehicleDamageScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("explosionRadius", missileSplashRadius, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("explosionInnerDamage", missileSplashDamage, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("explosionOuterDamage", missileSplashMinDamage, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("projectileSpeed", missileSpeed, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("projectileSpeedUp", missileVerticalSpeed, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("projectileModel", clipModel, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("projExplosionType", projectileExplosionType, WEAPON_PARSE_TYPE_GRENADE_TYPE),
    BG_WEAPON_PARSE_FIELD("projExplosionEffect", projectileExplosionEffect, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("projExplosionSound", projectileExplosionSound, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("projImpactExplode", projectileImpactExplode, PARSE_FIELD_BOOL),
    BG_WEAPON_PARSE_FIELD("projTrailEffect", projectileTrailEffect, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("projectileDLight", projectileDLight, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("projectileRed", projectileRed, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("projectileGreen", projectileGreen, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("projectileBlue", projectileBlue, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("projectileRadius", artilleryBarrageSpread, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("projectileCount", artilleryBarrageCount, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("projectileDelay", artilleryBarrageFirstDelay, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("projectileSpacingMin", artilleryBarrageDelayMin, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("projectileSpacingMax", artilleryBarrageDelayMax, PARSE_FIELD_INT),
    BG_WEAPON_PARSE_FIELD("adsTransInTime", adsInTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("adsTransOutTime", adsOutTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("adsIdleAmount", idleSwayAds, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsZoomFov", adsZoomFov, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsZoomInFrac", adsZoomInFrac, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsZoomOutFrac", adsZoomOutFrac, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsOverlayShader", adsOverlayShader, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("adsOverlayReticle", overlayReticleAdsReduceGate, WEAPON_PARSE_TYPE_OVERLAY_RETICLE),
    BG_WEAPON_PARSE_FIELD("adsOverlayWidth", adsOverlayWidth, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsOverlayHeight", adsOverlayHeight, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsBobFactor", adsBobFactor, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsViewBobMult", adsViewBobScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsAimPitch", adsPitchOffset, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsCrosshairInFrac", adsCrosshairInFrac, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsCrosshairOutFrac", adsCrosshairOutFrac, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSensitivity", adsSensitivity, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsReloadTransTime", reloadLoopTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("adsTransBlendTime", adsTransBlendTime, PARSE_FIELD_MILLISECONDS),
    BG_WEAPON_PARSE_FIELD("adsGunKickPitchMin", fireRecoilAdsPitchMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsGunKickPitchMax", fireRecoilAdsPitchMax, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsGunKickYawMin", fireRecoilAdsYawMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsGunKickYawMax", fireRecoilAdsYawMax, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsGunKickAccel", recoilReturnAds, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsGunKickSpeedMax", recoilVelocityAds, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsGunKickSpeedDecay", recoilDampingAds, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsGunKickStaticDecay", recoilFrictionAds, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsViewKickPitchMin", fireViewkickAdsPitchMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsViewKickPitchMax", fireViewkickAdsPitchMax, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsViewKickYawMin", fireViewkickAdsYawMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsViewKickYawMax", fireViewkickAdsYawMax, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsViewKickCenterSpeed", adsViewKickCenterSpeed, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSpread", adsSpread, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSpreadDucked", adsSpreadDucked, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("adsSpreadProne", adsSpreadProne, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadStandMin", hipSpreadStandMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadDuckedMin", hipSpreadDucked, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadProneMin", hipSpreadProne, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadMax", maxSpread, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadDecayRate", aimSpreadDecayRate, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadFireAdd", fireAimSpreadScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadTurnAdd", aimSpreadTurnRate, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadMoveAdd", aimSpreadMoveAdd, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadDuckedDecay", aimSpreadCrouchScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipSpreadProneDecay", aimSpreadProneScale, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipReticleSidePos", hipReticleSidePos, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipIdleAmount", idleSwayHip, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipGunKickPitchMin", fireRecoilHipPitchMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipGunKickPitchMax", fireRecoilHipPitchMax, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipGunKickYawMin", fireRecoilHipYawMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipGunKickYawMax", fireRecoilHipYawMax, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipGunKickAccel", recoilReturnHip, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipGunKickSpeedMax", recoilVelocityHip, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipGunKickSpeedDecay", recoilDampingHip, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipGunKickStaticDecay", recoilFrictionHip, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipViewKickPitchMin", fireViewkickHipPitchMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipViewKickPitchMax", fireViewkickHipPitchMax, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipViewKickYawMin", fireViewkickHipYawMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipViewKickYawMax", fireViewkickHipYawMax, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("hipViewKickCenterSpeed", hipViewKickCenterSpeed, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("leftArc", turretLeftArcDefault, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("rightArc", turretRightArcDefault, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("topArc", turretTopArcDefault, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("bottomArc", turretBottomArcDefault, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("accuracy", turretAccuracy, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("vertTurnSpeed", turretPitchRate, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("horTurnSpeed", turretYawRate, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("convergenceTime", turretConvergenceTime, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("maxRange", turretMaxRange, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("animHorRotateInc", animHorRotateInc, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("playerPositionDist", turretPlayerSpacing, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("stance", stance, WEAPON_PARSE_TYPE_STANCE),
    BG_WEAPON_PARSE_FIELD("useHintString", hintString, PARSE_FIELD_STRING_ALLOC),
    BG_WEAPON_PARSE_FIELD("turret_fov", adsZoomFov, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("minSpread", hipSpreadStandMin, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("maxSpread", maxSpread, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("horizViewJitter", horizViewJitter, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("vertViewJitter", vertViewJitter, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("fireHeat", turretHeatPerShot, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("cooldownRate", turretHeatDecay, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("aiEffectiveRange", aiEffectiveRange, PARSE_FIELD_FLOAT),
    BG_WEAPON_PARSE_FIELD("aiMissRange", aiMissRange, PARSE_FIELD_FLOAT),
};

typedef char bg_weapon_parse_field_count_check[
    (sizeof(bg_weaponParseFields) / sizeof(bg_weaponParseFields[0]) ==
     293)
        ? 1
        : -1];

#undef BG_WEAPON_PARSE_FIELD

/* Stock addresses each registration flag with scale four and clears this
 * 256-entry table as one 0x400-byte span. */
int32_t itemRegistered[ITEM_REGISTERED_COUNT];
const int bg_numItems = 134;
const int bg_iNumWeaponInfoFields = 293;
static const char *const bg_weaponClassNames[WEAPCLASS_COUNT] = {
    "rifle",
    "mg",
    "smg",
    "lmg",
    "pistol",
    "grenade",
    "rocketlauncher",
    "turret",
    "spotter",
    "non-player",
    "flamethrower",
}; /* PTR_s_rifle_000bb4e0 */

static const char *const bg_weaponAmmoTypeNames[WEAPON_AMMO_TYPE_COUNT] = {
    "smg",
    "pistol",
    "rifle",
    "lmg",
    "hmg",
    "umg",
}; /* PTR_DAT_000bb50c */

static const char *const bg_weaponOverlayReticleNames[WEAPON_OVERLAY_RETICLE_COUNT] = {
    "none",
    "crosshair",
    "FG42",
    "Springfield",
    "Gewehr43",
}; /* PTR_DAT_000bb47c */

static const char *const bg_weaponStanceNames[WEAPON_STANCE_COUNT] = {
    "stand",
    "duck",
    "prone",
}; /* PTR_s_stand_000bb4c0 */

static const char *const bg_weaponGrenadeTypeNames[WEAPON_GRENADE_TYPE_COUNT] = {
    "grenade",
    "smoke",
    "rocket",
    "molotov",
    "artillery",
    "mortar",
    "tank",
    "b17",
    "none",
}; /* PTR_s_grenade_000bb540 */

/* NOT_FROM_ORIGINAL_SOURCE: local item table accessor used by recovered item
 * code; no standalone generated entry. Decompiler evidence indexes
 * `bg_itemlist + index * 0x30`, which is source-level `bg_itemlist[index]`
 * for the original i386 sizeof(gitem_t). */
static gitem_t *game_compat_item_def_for_index(int itemIndex)
{
    return &bg_itemlist[itemIndex];
}

/* NOT_FROM_ORIGINAL_SOURCE: inverse of game_compat_item_def_for_index; uses the original
 * item-table pointer arithmetic recovered from the decompiler. */
static int game_compat_item_index_from_def(const gitem_t *item)
{
    return (int)(item - bg_itemlist);
}

static const char *const bg_stockWeaponItemClassnames[] = {
    "emptyitem_\"w01\"",
    "emptyitem_\"w02\"",
    "emptyitem_\"w03\"",
    "emptyitem_\"w04\"",
    "emptyitem_\"w05\"",
    "emptyitem_\"w06\"",
    "emptyitem_\"w07\"",
    "emptyitem_\"w08\"",
    "emptyitem_\"w09\"",
    "emptyitem_\"w10\"",
    "emptyitem_\"w11\"",
    "emptyitem_\"w12\"",
    "emptyitem_\"w13\"",
    "emptyitem_\"w14\"",
    "emptyitem_\"w15\"",
    "emptyitem_\"w16\"",
    "emptyitem_\"w17\"",
    "emptyitem_\"w18\"",
    "emptyitem_\"w19\"",
    "emptyitem_\"w20\"",
    "emptyitem_\"w21\"",
    "emptyitem_\"w22\"",
    "emptyitem_\"w23\"",
    "emptyitem_\"w24\"",
    "emptyitem_\"w25\"",
    "emptyitem_\"w26\"",
    "emptyitem_\"w27\"",
    "emptyitem_\"w28\"",
    "emptyitem_\"w29\"",
    "emptyitem_\"w30\"",
    "emptyitem_\"w31\"",
    "emptyitem_\"w32\"",
    "emptyitem_\"w33\"",
    "emptyitem_\"w34\"",
    "emptyitem_\"w35\"",
    "emptyitem_\"w36\"",
    "emptyitem_\"w37\"",
    "emptyitem_\"w38\"",
    "emptyitem_\"w39\"",
    "emptyitem_\"w40\"",
    "emptyitem_\"w41\"",
    "emptyitem_\"w42\"",
    "emptyitem_\"w43\"",
    "emptyitem_\"w44\"",
    "emptyitem_\"w45\"",
    "emptyitem_\"w46\"",
    "emptyitem_\"w47\"",
    "emptyitem_\"w48\"",
    "emptyitem_\"w49\"",
    "emptyitem_\"w50\"",
    "emptyitem_\"w51\"",
    "emptyitem_\"w52\"",
    "emptyitem_\"w53\"",
    "emptyitem_\"w54\"",
    "emptyitem_\"w55\"",
    "emptyitem_\"w56\"",
    "emptyitem_\"w57\"",
    "emptyitem_\"w58\"",
    "emptyitem_\"w59\"",
    "emptyitem_\"w60\"",
    "emptyitem_\"w61\"",
    "emptyitem_\"w62\"",
    "emptyitem_\"w63\"",
    "emptyitem_\"w64\"",
    "emptyitem_\"w65\"",
    "emptyitem_\"w66\"",
    "emptyitem_\"w67\"",
    "emptyitem_\"w68\"",
    "emptyitem_\"w69\"",
    "emptyitem_\"w70\"",
    "emptyitem_\"w71\"",
    "emptyitem_\"w72\"",
    "emptyitem_\"w73\"",
    "emptyitem_\"w74\"",
    "emptyitem_\"w75\"",
    "emptyitem_\"w76\"",
    "emptyitem_\"w77\"",
    "emptyitem_\"w78\"",
    "emptyitem_\"w79\"",
    "emptyitem_\"w80\"",
    "emptyitem_\"w81\"",
    "emptyitem_\"w82\"",
    "emptyitem_\"w83\"",
    "emptyitem_\"w84\"",
    "emptyitem_\"w85\"",
    "emptyitem_\"w86\"",
    "emptyitem_\"w87\"",
    "emptyitem_\"w88\"",
    "emptyitem_\"w89\"",
    "emptyitem_\"w90\"",
    "emptyitem_\"w91\"",
    "emptyitem_\"w92\"",
    "emptyitem_\"w93\"",
    "emptyitem_\"w94\"",
    "emptyitem_\"w95\"",
    "emptyitem_\"w96\"",
    "emptyitem_\"w97\"",
    "emptyitem_\"w98\"",
    "emptyitem_\"w99\"",
    "emptyitem_\"w100\"",
    "emptyitem_\"w101\"",
    "emptyitem_\"w102\"",
    "emptyitem_\"w103\"",
    "emptyitem_\"w104\"",
    "emptyitem_\"w105\"",
    "emptyitem_\"w106\"",
    "emptyitem_\"w107\"",
    "emptyitem_\"w108\"",
    "emptyitem_\"w109\"",
    "emptyitem_\"w110\"",
    "emptyitem_\"w111\"",
    "emptyitem_\"w112\"",
    "emptyitem_\"w113\"",
    "emptyitem_\"w114\"",
    "emptyitem_\"w115\"",
    "emptyitem_\"w116\"",
    "emptyitem_\"w117\"",
    "emptyitem_\"w118\"",
    "emptyitem_\"w119\"",
    "emptyitem_\"w120\"",
    "emptyitem_\"w121\"",
    "emptyitem_\"w122\"",
    "emptyitem_\"w123\"",
    "emptyitem_\"w124\"",
    "emptyitem_\"w125\"",
    "emptyitem_\"w126\"",
    "emptyitem_\"w127\"",
    "emptyitem_\"w128\"",
};

static const bg_stock_item_seed_entry_t bg_stockFixedItemSeeds[] = {
    { 129,
      { "item_ammo_stielhandgranate_open", "grenade_pickup",
        "xmodel/ammo_stielhandgranate1", NULL,
        "gfx/icons/hud@steilhandgrenate", "gfx/icons/hud@steilhandgrenate",
        "Stielhandgranate_mp Ammo Open", 10, IT_AMMO, -1, -1, -1 } },
    { 130,
      { "item_ammo_stielhandgranate_closed", "grenade_pickup",
        "xmodel/ammo_stielhandgranate2", NULL,
        "gfx/icons/hud@steilhandgrenate", "gfx/icons/hud@steilhandgrenate",
        "Stielhandgranate_mp Ammo Closed", 10, IT_AMMO, -1, -1, -1 } },
    { 131,
      { "item_health_small", "health_pickup_small", "xmodel/health_small", NULL,
        "icons/iconh_small", NULL, "Small Health", 10, IT_HEALTH, 0, 0,
        0 } },
    { 132,
      { "item_health", "health_pickup_medium", "xmodel/health_medium", NULL,
        "icons/iconh_med", NULL, "Med Health", 25, IT_HEALTH, 0, 0,
        0 } },
    { 133,
      { "item_health_large", "health_pickup_large", "xmodel/health_large",
        NULL, "icons/iconh_large", NULL, "Large Health", 50,
        IT_HEALTH, 0, 0, 0 } },
};

/* NOT_FROM_ORIGINAL_SOURCE: local stock item table seeding helper; no standalone generated entry. */
static void game_compat_bg_write_stock_item_seed(int itemIndex,
                                  const bg_stock_item_seed_t *seed)
{
    gitem_t *item = game_compat_item_def_for_index(itemIndex);

    item->classname = seed->classname;
    item->pickupSound = seed->pickupSound;
    item->worldModel = seed->worldModel;
    item->iconModel = seed->iconModel;
    item->hudIcon = seed->hudIcon;
    item->ammoIcon = seed->ammoIcon;
    item->pickupName = seed->pickupName;
    item->quantity = seed->quantity;
    item->type = seed->type;
    item->weapon = seed->weapon;
    item->ammoIndex = seed->ammoIndex;
    item->clipIndex = seed->clipIndex;
}

/* NOT_FROM_ORIGINAL_SOURCE: local stock item table seeding helper; no standalone generated entry. */
static void game_compat_bg_init_stock_item_list(void)
{
    memset(bg_itemlist, 0, sizeof(bg_itemlist[0]) * (ITEM_INDEX_MAX + 2));

    for (int itemIndex = 1; itemIndex <= 128; itemIndex++) {
        bg_stock_item_seed_t seed = {
            bg_stockWeaponItemClassnames[itemIndex - 1],
            "",
            "",
            "",
            "",
            "",
            "",
            0,
            IT_BAD,
            0,
            0,
            0
        };

        game_compat_bg_write_stock_item_seed(itemIndex, &seed);
    }

    for (size_t seedIndex = 0;
         seedIndex < sizeof(bg_stockFixedItemSeeds) /
                         sizeof(bg_stockFixedItemSeeds[0]);
         seedIndex++) {
        game_compat_bg_write_stock_item_seed(bg_stockFixedItemSeeds[seedIndex].itemIndex,
                              &bg_stockFixedItemSeeds[seedIndex].item);
    }
}

/* VERIFIED_DECOMPILER(0x1fa6c, 2fa6c_BG_FindItemForWeapon.c, VERIFY-WAVE3-ITEM-TOUCH-VISIBILITY-2026-06-17): DATAFLOW_VERIFIED - weapon index bounds, error path, and bg_itemlist index return checked against current decompiler output. */
gitem_t *BG_FindItemForWeapon(int weapon)
{
    if (weapon < 0 || BG_GetNumWeapons() < weapon) {
        Com_Error(ERROR_DROP,
                  COM_ERROR_MARKER
                  "BG_FindItemForWeapon: weapon out of range %i",
                  weapon);
    }

    return game_compat_item_def_for_index(weapon);
}

/* VERIFIED_DECOMPILER(0x1faca, 2faca_BG_FindItem.c, VERIFY-WAVE3-ITEM-TOUCH-VISIBILITY-2026-06-17): DATAFLOW_VERIFIED - 1..133 scan, weapon pickup-name lookup, item pickup/classname lookup, and NULL return checked against current decompiler output. */
gitem_t *BG_FindItem(const char *pickupName)
{
    for (int itemIndex = ITEM_INDEX_MIN; itemIndex < bg_numItems; itemIndex++) {
        gitem_t *item = game_compat_item_def_for_index(itemIndex);

        if (itemIndex <= BG_GetNumWeapons()) {
            const weaponInfo_t *weaponInfo =
                (const weaponInfo_t *)BG_GetInfoForWeapon(itemIndex);
            if (Q_stricmp(pickupName, weaponInfo->pickupName) == 0) {
                return item;
            }
        } else {
            if (Q_stricmp(item->pickupName, pickupName) == 0 ||
                Q_stricmp(item->classname,
                          pickupName) == 0) {
                return item;
            }
        }
    }

    return NULL;
}

/* VERIFIED_DECOMPILER(0x1fba8, 2fba8_BG_PlayerTouchesItem.c, VERIFY-WAVE3-ITEM-TOUCH-VISIBILITY-2026-06-17): DATAFLOW_VERIFIED - trajectory evaluation, XY/Z touch ranges, and boolean return checked against current decompiler output. */
int BG_PlayerTouchesItem(gclient_t *client, gentity_t *itemEnt, int time)
{
    vec3_t itemOrigin;

    BG_EvaluateTrajectory(&itemEnt->pos, time, itemOrigin);

    if (client->ps.psOrigin[0] - itemOrigin[0] > ITEM_TOUCH_XY_RANGE ||
        client->ps.psOrigin[0] - itemOrigin[0] < -ITEM_TOUCH_XY_RANGE ||
        client->ps.psOrigin[1] - itemOrigin[1] > ITEM_TOUCH_XY_RANGE ||
        client->ps.psOrigin[1] - itemOrigin[1] < -ITEM_TOUCH_XY_RANGE ||
        client->ps.psOrigin[2] - itemOrigin[2] > ITEM_TOUCH_Z_ABOVE ||
        client->ps.psOrigin[2] - itemOrigin[2] < -ITEM_TOUCH_Z_BELOW) {
        return qfalse;
    }

    return qtrue;
}

/* NOT_FROM_ORIGINAL_SOURCE: local configstring formatting helper factored from RegisterItem/SaveRegisteredItems. */
static char game_compat_registered_items_config_char(int value)
{
    if (value < 10) {
        return (char)('0' + value);
    }
    return (char)('W' + value);
}

#if !EMULATE_X87
/* NOT_FROM_ORIGINAL_SOURCE: local rand scaling helper factored from generated item
 * code.  Under EMULATE_X87 callers use the *X87 variants below instead. */
static long double game_compat_item_random_half_open_extended(void)
{
    return (long double)rand() / (long double)RAND_FLOAT_DENOM;
}

/* NOT_FROM_ORIGINAL_SOURCE: local rand scaling helper factored from generated item code. */
static long double game_compat_item_random_signed(void)
{
    long double value = game_compat_item_random_half_open_extended();

    return value + value - 1.0L;
}
#endif

#if EMULATE_X87
/* NOT_FROM_ORIGINAL_SOURCE: shim variants for the stock inline `fild rand`
 * (direct, exact) / DENOM(0x4f000000).
 * long double can't carry 80 bits on arm64, so callers build the x87 chain from
 * these instead. */
static x87f game_compat_item_random_half_open_extended_x87(void)
{
    return x87f_div(x87f_load_i32(rand()), x87f_load_f32(RAND_FLOAT_DENOM));
}

/* NOT_FROM_ORIGINAL_SOURCE: signed companion to the x87 rand shim above. */
static x87f game_compat_item_random_signed_x87(void)
{
    x87f value = game_compat_item_random_half_open_extended_x87();
    return x87f_sub(x87f_add(value, value), x87f_load_f32(1.0f));
}
#endif

/* NOT_FROM_ORIGINAL_SOURCE: local rand scaling helper factored from generated bounce code. */
static float game_compat_item_random_settled_height(void)
{
    /* Stock 0x566a8: fild rand /DENOM * 0.5f + 0.5f, one float store. */
#if EMULATE_X87
    return x87f_store_f32(x87f_add(
        x87f_mul(game_compat_item_random_half_open_extended_x87(),
                 x87f_load_f32(ITEM_BOUNCE_SETTLE_RANDOM_Z)),
        x87f_load_f32(ITEM_BOUNCE_SETTLE_MIN_Z)));
#else
    return (float)(game_compat_item_random_half_open_extended() *
                       (long double)ITEM_BOUNCE_SETTLE_RANDOM_Z +
                   (long double)ITEM_BOUNCE_SETTLE_MIN_Z);
#endif
}

/* NOT_FROM_ORIGINAL_SOURCE: names the Linux gentity +0x17e item auto-touch
 * byte without changing the shared mover/vehicle active-state layout. */
static uint8_t *game_compat_item_auto_touch_flag(gentity_t *ent)
{
    return &ent->activeState;
}

/* NOT_FROM_ORIGINAL_SOURCE: local cvar clamp helper factored from generated drop code. */
static int game_compat_dropped_weapon_slot_limit(void)
{
    int limit = g_maxDroppedWeapons.integer;

    if (limit < DROPPED_WEAPON_MIN_SLOTS) {
        return DROPPED_WEAPON_MIN_SLOTS;
    }
    if (limit > DROPPED_WEAPON_SLOT_COUNT) {
        return DROPPED_WEAPON_SLOT_COUNT;
    }
    return limit;
}

/* NOT_FROM_ORIGINAL_SOURCE: local bounds helper factored from generated spawn/drop code. */
static void game_compat_set_item_bounds(gentity_t *ent, itemType_t itemType)
{
    float *mins = ent->mins;
    float *maxs = ent->maxs;

    mins[0] = -1.0f;
    mins[1] = -1.0f;
    maxs[0] = 1.0f;
    maxs[1] = 1.0f;

    if (itemType == IT_WEAPON) {
        mins[2] = -1.0f;
        maxs[2] = 1.0f;
    } else {
        mins[2] = 0.0f;
        maxs[2] = 2.0f;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from GetFreeCueSpot client-state checks. */
static qboolean game_compat_level_client_is_active(int clientNum)
{
    gclient_t *client = &level.clients[clientNum];
    return client->connectedState == CON_CONNECTED &&
           client->sessionState == SESS_STATE_PLAYING;
}

/* VERIFIED_DECOMPILER(0x54d84, 64d84_FUN_00064d84.c, VERIFY-PACKET-ITEM-LIFECYCLE-2026-06-17): DATAFLOW_VERIFIED - slot clamp, empty-slot return, active-client nearest-distance scan, farthest occupied slot selection, delayed free scheduling, and return slot checked against current decompiler output. */
int GetFreeCueSpot(void)
{
    int slotLimit = game_compat_dropped_weapon_slot_limit();
    int farthestSlot = 0;
    float farthestNearestDistance = -1.0f;

    for (int slot = 0; slot < slotLimit; slot++) {
        gentity_t *dropped = level.droppedWeaponSlots[slot];
        float nearestClientDistance = 9.99998e11f; /* original float32 0x5368d487 */

        if (dropped == 0) {
            return slot;
        }

        for (int clientNum = 0; clientNum < level.maxclients; clientNum++) {
            if (game_compat_level_client_is_active(clientNum)) {
                float distance = (float)VectorDistanceSquared(
                    g_entities[clientNum].currentOrigin,
                    dropped->currentOrigin);

                if (distance < nearestClientDistance) {
                    nearestClientDistance = distance;
                }
            }
        }

        if (farthestNearestDistance < nearestClientDistance) {
            farthestNearestDistance = nearestClientDistance;
            farthestSlot = slot;
        }
    }

    level.droppedWeaponSlots[farthestSlot]->think = G_FreeEntity;
    level.droppedWeaponSlots[farthestSlot]->nextthink = coduo_int32_from_bits(
        (uint32_t)level.time + UINT32_C(1));
    return farthestSlot;
}

/* VERIFIED_DECOMPILER(0x54f16, 64f16_DroppedItemClearOwner.c, VERIFY-PACKET-ITEM-LIFECYCLE-2026-06-17): DATAFLOW_VERIFIED - callback signature and +0x94 owner/clientNum store to ENTITYNUM_WORLD checked against current decompiler output. */
void DroppedItemClearOwner(gentity_t *ent)
{
    ent->clientNum = ENTITYNUM_WORLD;
}

/* VERIFIED_DECOMPILER(0x545db, 645db_RespawnItem.c, VERIFY-WAVE3-ITEM-TOUCH-VISIBILITY-2026-06-17): DATAFLOW_VERIFIED - respawn team selection, visibility/content flag restore, link, respawn event, and nextthink clear checked against current decompiler output. */
void RespawnItem(gentity_t *ent)
{
    if ((uint16_t)ent->teamName != 0) {
        gentity_t *master = ent->teamMaster;
        int teamCount = 0;
        int selected;

        if (master == 0) {
            G_Error("RespawnItem: bad teammaster");
        }

        for (gentity_t *candidate = master; candidate != 0;
             candidate = candidate->teamChain) {
            teamCount++;
        }

        selected = rand() % teamCount;
        ent = master;
        for (int index = 0; index < selected; index++) {
            ent = ent->teamChain;
        }
    }

    ent->scriptContents = ITEM_RESPAWN_PACKED_STATE;
    ent->flags &= ~FL_NOCLIENT;
    ent->svFlags &= ~ITEM_SVF_RESPAWNING;
    trap_LinkEntity(ent);
    G_AddEvent(ent, EV_ITEM_RESPAWN, 0);
    ent->nextthink = 0;
}

/* VERIFIED_DECOMPILER(0x54700, 64700_Touch_Item_Auto.c, VERIFY-WAVE3-ITEM-TOUCH-VISIBILITY-2026-06-17): DATAFLOW_VERIFIED - +0x17e auto-touch byte store and Touch_Item argument order checked against current decompiler output. */
void Touch_Item_Auto(gentity_t *itemEnt, gentity_t *other, int traceMode)
{
    *game_compat_item_auto_touch_flag(itemEnt) = 1;
    Touch_Item(itemEnt, other, traceMode);
}

/* VERIFIED_DECOMPILER(0x559af, 659af_Use_Item.c, VERIFY-WAVE3-ITEM-TOUCH-VISIBILITY-2026-06-17): DATAFLOW_VERIFIED - generated body reads only the item entity and calls RespawnItem; three-argument signature preserves the +0x220 use callback ABI. */
void Use_Item(gentity_t *ent, gentity_t *other, gentity_t *activator)
{
    (void)other;
    (void)activator;
    RespawnItem(ent);
}

/* VERIFIED_DECOMPILER(0x55d33, 65d33_ClearRegisteredItems.c, VERIFY-P1-ITEMS-2026-06-17): DATAFLOW_VERIFIED - memset length, forced slot zero registration, and return behavior checked against current decompiler output. */
void ClearRegisteredItems(void)
{
    memset(itemRegistered, 0, sizeof(itemRegistered));
    itemRegistered[0] = 1;
}

/* VERIFIED_DECOMPILER(0x55d75, 65d75_SaveRegisteredItems.c, VERIFY-P1-ITEMS-2026-06-17): DATAFLOW_VERIFIED - dirty flag clear, registered-item packing loop, trailing nibble handling, terminator, and configstring write checked against current decompiler output. */
void SaveRegisteredItems(void)
{
    char config[276];
    int out = 0;
    int bits = 0;
    int bit = 0;

    level.registeredItemsDirty = 0;

    for (int itemIndex = 0; itemIndex < bg_numItems; itemIndex++) {
        if (itemRegistered[itemIndex] != 0) {
            bits += 1 << bit;
        }

        bit++;
        if (bit == ITEM_REGISTERED_NIBBLE_BITS) {
            config[out++] = game_compat_registered_items_config_char(bits);
            bits = 0;
            bit = 0;
        }
    }

    if (bit != 0) {
        config[out++] = game_compat_registered_items_config_char(bits);
    }

    config[out] = '\0';
    trap_SetConfigstring(ITEM_CONFIGSTRING_REGISTERED, config);
}

/* VERIFIED_DECOMPILER(0x55f2a, 65f2a_RegisterItem.c, VERIFY-P1-ITEMS-2026-06-17): DATAFLOW_VERIFIED - duplicate guard, late-registration error path, weapon alt-chain registration, model precache calls, and dirty flag checked against current decompiler output. */
void RegisterItem(int itemIndex, int updateConfigString)
{
    if (itemRegistered[itemIndex] != 0) {
        return;
    }

    if (level.spawning == 0) {
        const char *name = game_compat_item_def_for_index(itemIndex)->pickupName;

        if ((name == 0 || *name == '\0') && itemIndex <= BG_GetNumWeapons()) {
            name = ((const weaponInfo_t *)BG_GetInfoForWeapon(itemIndex))->pickupName;
        }

        if (name == 0 || *name == '\0') {
            /* Intentional runtime fallback for late item-registration errors. */
            name = "<<unknown>>";
        }

        Scr_Error(va("game tried to register the item '%s' after initialization finished\n",
                     name));
    }

    itemRegistered[itemIndex] = 1;

    if (game_compat_item_def_for_index(itemIndex)->type == IT_WEAPON) {
        int weapon = itemIndex;

        do {
            const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(weapon);

            itemRegistered[weapon] = 1;
            G_ModelIndex(weaponInfo->worldModel);
            G_ModelIndex(weaponInfo->viewModel);
            G_ModelIndex(weaponInfo->pickupModel);
            G_ModelIndex(weaponInfo->clipModel);

            weapon = weaponInfo->altWeapon;
            if (weapon == 0) {
                break;
            }
        } while (weapon != itemIndex);
    } else {
        const char *worldModel = game_compat_item_def_for_index(itemIndex)->worldModel;
        const char *iconModel = game_compat_item_def_for_index(itemIndex)->iconModel;

        if (worldModel != 0) {
            G_ModelIndex(worldModel);
        }
        if (iconModel != 0) {
            G_ModelIndex(iconModel);
        }
    }

    if (updateConfigString != 0) {
        level.registeredItemsDirty = 1;
    }
}

/* VERIFIED_DECOMPILER(0x56128, 66128_IsItemRegistered.c, VERIFY-P1-ITEMS-2026-06-17): DATAFLOW_VERIFIED - itemRegistered indexed return checked against current decompiler output. */
qboolean IsItemRegistered(int itemIndex)
{
    return itemRegistered[itemIndex];
}

/* VERIFIED_DECOMPILER(0x559d2, 659d2_FinishSpawningItem.c, VERIFY-P1-ITEMS-2026-06-17): DATAFLOW_VERIFIED - item type lookup, floor trace, startsolid retry/free path, ground entity, origin/angle placement, weapon roll adjust, and link side effect checked against current decompiler output. */
void FinishSpawningItem(gentity_t *ent)
{
    int itemIndex = ent->itemIndex;
    itemType_t itemType = game_compat_item_def_for_index(itemIndex)->type;

    if ((ent->spawnflags &
         ITEM_SPAWNFLAG_NO_DROP_TO_FLOOR) == 0) {
        trace_t trace;
        vec3_t end;

        game_compat_set_item_bounds(ent, itemType);
        ent->svFlags |= SVF_CAPSULE;
        ent->s_flags |= EF_CAPSULE;

        end[0] = ent->currentOrigin[0];
        end[1] = ent->currentOrigin[1];
        end[2] = ent->currentOrigin[2] - ITEM_DROP_TRACE_DISTANCE;

        trap_TraceCapsule(&trace, ent->currentOrigin,
                          ent->mins,
                          ent->maxs,
                          end, ent->s_number, ITEM_TRACE_CONTENTS);

        if (trace.startsolid != 0) {
            vec3_t retryStart;

            retryStart[0] = ent->currentOrigin[0];
            retryStart[1] = ent->currentOrigin[1];
            retryStart[2] = ent->currentOrigin[2] - ITEM_STARTSOLID_RETRY_Z;
            trap_TraceCapsule(&trace, retryStart,
                              ent->mins,
                              ent->maxs,
                              end, ent->s_number, ITEM_TRACE_CONTENTS);
        }

        if (trace.startsolid != 0) {
            G_Printf("FinishSpawningItem: %s startsolid at %s\n",
                     SL_ConvertToString(ent->scriptClassname),
                     vtos(ent->currentOrigin));
            G_FreeEntity(ent);
            return;
        }

        ent->groundEntityNum = trace.entityNum;
        G_SetOrigin(ent, trace.endpos);

        if (trace.fraction < 1.0f) {
            float axis[3][3];
            vec3_t angles;

            axis[2][0] = trace.normal[0];
            axis[2][1] = trace.normal[1];
            axis[2][2] = trace.normal[2];
            AngleVectors(ent->currentAngles, axis[0], 0, 0);
            CrossProduct(axis[2], axis[0], axis[1]);
            CrossProduct(axis[1], axis[2], axis[0]);
            AxisToAngles(&axis[0][0], angles);

            if (itemType == IT_WEAPON) {
                angles[2] += ITEM_WEAPON_ANGLE_ADJUST;
            }

            G_SetAngle(ent, angles);
        }
    } else {
        G_SetOrigin(ent, ent->currentOrigin);
    }

    trap_LinkEntity(ent);
}

/* VERIFIED_DECOMPILER(0x54f28, 64f28_LaunchItem.c, VERIFY-P1-ITEMS-2026-06-17): DATAFLOW_VERIFIED - registration, free dropped slot, entity fields, model/dobj setup, gravity trajectory, owner clear think, flags, link, and return checked against current decompiler output. */
gentity_t *LaunchItem(gitem_t *item, const float *origin, const float *velocity,
                      int ownerNum)
{
    int itemIndex = game_compat_item_index_from_def(item);
    itemType_t itemType = game_compat_item_def_for_index(itemIndex)->type;
    gentity_t *dropped;
    int slot;

    RegisterItem(itemIndex, 1);

    dropped = G_Spawn();
    slot = GetFreeCueSpot();
    level.droppedWeaponSlots[slot] = dropped;

    dropped->s_eType = ET_ITEM;
    dropped->itemIndex = itemIndex;
    G_SetConstString(&dropped->scriptClassname, item->classname);
    dropped->itemInfo = item;
    game_compat_set_item_bounds(dropped, itemType);
    dropped->svFlags |= SVF_CAPSULE;
    dropped->s_flags |= EF_CAPSULE;
    dropped->scriptContents = ITEM_SPAWN_PACKED_STATE;
    dropped->clipmask =
        DROPPED_ITEM_CLIPMASK;
    dropped->clientNum = ownerNum;

    G_SetModel(dropped, game_compat_item_def_for_index(itemIndex)->worldModel);
    G_DObjUpdate(dropped);
    dropped->touch = Touch_Item_Auto;
    G_SetOrigin(dropped, origin);

    dropped->pos.trType =
        TR_GRAVITY;
    dropped->pos.trTime = level.time;
    dropped->pos.trDelta[0] = velocity[0];
    dropped->pos.trDelta[1] = velocity[1];
    dropped->pos.trDelta[2] = velocity[2];

    dropped->think = DroppedItemClearOwner;
    dropped->nextthink = level.time + DROPPED_ITEM_CLEAR_OWNER_MS;
    dropped->flags = DROPPED_ITEM_FLAGS;
    trap_LinkEntity(dropped);
    return dropped;
}

/* VERIFIED_DECOMPILER(0x5518c, 6518c_Drop_Item.c, VERIFY-P1-ITEMS-2026-06-17): DATAFLOW_VERIFIED - throw angle, stationary/nonstationary velocity, randomized upward velocity, origin midpoint, and LaunchItem arguments checked against current decompiler output. */
gentity_t *Drop_Item(gentity_t *ent, gitem_t *item, float angleOffset,
                     int stationary)
{
    vec3_t angles;
    vec3_t velocity;
    vec3_t origin;

    angles[0] = 0.0f;
    angles[1] = ent->currentAngles[1] + angleOffset;
    angles[2] = 0.0f;

    if (stationary == 0) {
        AngleVectors(angles, velocity, 0, 0);
        velocity[0] *= DROPPED_ITEM_THROW_SPEED;
        velocity[1] *= DROPPED_ITEM_THROW_SPEED;
        velocity[2] *= DROPPED_ITEM_THROW_SPEED;
        /* randSigned*RANDOM + THROW_UP kept 80-bit, + velocity[2], one store. */
#if EMULATE_X87
        velocity[2] = x87f_store_f32(x87f_add(
            x87f_add(x87f_mul(game_compat_item_random_signed_x87(),
                              x87f_load_f32(DROPPED_ITEM_THROW_UP_RANDOM)),
                     x87f_load_f32(DROPPED_ITEM_THROW_UP)),
            x87f_load_f32(velocity[2])));
#else
        velocity[2] +=
            game_compat_item_random_signed() * DROPPED_ITEM_THROW_UP_RANDOM +
            DROPPED_ITEM_THROW_UP;
#endif
    } else {
        velocity[0] = 0.0f;
        velocity[1] = 0.0f;
        velocity[2] = 0.0f;
    }

    origin[0] = ent->currentOrigin[0];
    origin[1] = ent->currentOrigin[1];
    /* (maxs[2]-mins[2])*0.5 kept 80-bit, + currentOrigin[2], one store. */
#if EMULATE_X87
    origin[2] = x87f_store_f32(x87f_add(
        x87f_mul(x87f_sub(x87f_load_f32(ent->maxs[2]), x87f_load_f32(ent->mins[2])),
                 x87f_load_f32(0.5f)),
        x87f_load_f32(ent->currentOrigin[2])));
#else
    origin[2] =
        ent->currentOrigin[2] +
        (ent->maxs[2] -
         ent->mins[2]) *
            0.5f;
#endif

    return LaunchItem(item, origin, velocity, ent->s_number);
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Drop_Weapon generated body. */
static void game_compat_drop_weapon_default_ammo(int clipIndex, int *ammoCount, int *clipCount)
{
    int clipSize = BG_GetAmmoClipSize(clipIndex);
    /* Stock (v+1.0)*0.5 rounded once to float; (clipSize-1) fild'd raw *
     * ammoScale; ammoCount fild'd raw * (v*0.5+0.25) -> shim chains. */
#if EMULATE_X87
    float ammoScale = x87f_store_f32(x87f_mul(
        x87f_add(game_compat_item_random_half_open_extended_x87(), x87f_load_f32(1.0f)),
        x87f_load_f32(0.5f)));

    *ammoCount = Game_RoundFloatPlusHalf(x87f_store_f32(x87f_mul(
                     x87f_load_i32(clipSize - 1), x87f_load_f32(ammoScale)))) +
                 1;
    *clipCount = Game_RoundFloatPlusHalf(x87f_store_f32(x87f_mul(
        x87f_load_i32(*ammoCount),
        x87f_add(x87f_mul(game_compat_item_random_half_open_extended_x87(), x87f_load_f32(0.5f)),
                 x87f_load_f32(0.25f)))));
#else
    float ammoScale =
        (float)((game_compat_item_random_half_open_extended() + 1.0L) * 0.5L);

    *ammoCount = Game_RoundFloatPlusHalf(
                     (float)((long double)(clipSize - 1) *
                             (long double)ammoScale)) +
                 1;
    *clipCount = Game_RoundFloatPlusHalf(
        (float)((long double)*ammoCount *
                (game_compat_item_random_half_open_extended() * 0.5L + 0.25L)));
#endif
    *ammoCount -= *clipCount;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Drop_Weapon generated body. */
static void game_compat_drop_weapon_ranged_ammo(int clipIndex, int minAmmo, int maxAmmo,
                                 int *ammoCount, int *clipCount)
{
    int totalAmmo;

    if (maxAmmo == minAmmo) {
        totalAmmo = minAmmo;
    } else {
        totalAmmo = minAmmo + rand() % (maxAmmo - minAmmo);
    }

    *ammoCount = totalAmmo;
    if (totalAmmo < 1) {
        *ammoCount = 0;
        *clipCount = 0;
        return;
    }

    totalAmmo = BG_GetAmmoClipSize(clipIndex);
    if (totalAmmo == 0) {
        *clipCount = 0;
    } else {
        *clipCount = rand() % totalAmmo;
    }

    if (*clipCount < *ammoCount) {
        *ammoCount -= *clipCount;
    } else {
        *clipCount = *ammoCount;
        *ammoCount = 0;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Drop_Weapon generated body. */
static void game_compat_drop_weapon_synthetic_ammo(int weapon, int clipIndex, int *ammoCount,
                                    int *clipCount)
{
    int maxAmmo = ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->dropAmmoMax;
    int minAmmo = ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->dropAmmoMin;

    if (maxAmmo < minAmmo) {
        int oldMax = maxAmmo;

        maxAmmo = minAmmo;
        minAmmo = oldMax;
    }

    if (maxAmmo == 0 && minAmmo == 0) {
        game_compat_drop_weapon_default_ammo(clipIndex, ammoCount, clipCount);
    } else if (maxAmmo < 0) {
        *ammoCount = 0;
        *clipCount = 0;
    } else {
        game_compat_drop_weapon_ranged_ammo(clipIndex, minAmmo, maxAmmo, ammoCount, clipCount);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Drop_Weapon generated body. */
static void game_compat_drop_weapon_place_at_tag(gentity_t *source, gentity_t *dropped,
                                 const char *tagName)
{
    float tagMatrix[16];
    vec3_t tagAngles;

    if (G_DObjGetWorldTagMatrix(source, tagName, tagMatrix) != 0) {
        trace_t trace;
        vec3_t start;

        /* Original i386 Drop_Weapon 0x556fc..0x557b3 rounds each stage to
         * float per component: mins+maxs stored, then *0.5f stored, then
         * +currentOrigin stored. Keep the three-pass store structure. */
        start[0] = source->mins[0] + source->maxs[0];
        start[1] = source->mins[1] + source->maxs[1];
        start[2] = source->mins[2] + source->maxs[2];
        start[0] *= 0.5f;
        start[1] *= 0.5f;
        start[2] *= 0.5f;
        start[0] += source->currentOrigin[0];
        start[1] += source->currentOrigin[1];
        start[2] += source->currentOrigin[2];

        trap_TraceCapsule(&trace, start,
                          dropped->mins,
                          dropped->maxs,
                          &tagMatrix[12], source->s_number,
                          ITEM_TRACE_CONTENTS);

        dropped->pos.trBase[0] = trace.endpos[0];
        dropped->pos.trBase[1] = trace.endpos[1];
        dropped->pos.trBase[2] = trace.endpos[2];
        dropped->currentOrigin[0] = trace.endpos[0];
        dropped->currentOrigin[1] = trace.endpos[1];
        dropped->currentOrigin[2] = trace.endpos[2];
        dropped->pos.trTime = level.time;
        Axis4ToAngles(tagMatrix, tagAngles);
    } else {
        tagAngles[0] = source->currentAngles[0];
        tagAngles[1] = source->currentAngles[1];
        tagAngles[2] = source->currentAngles[2];
    }

    /* ORIGINAL_BINARY_BUG(game-drop-weapon-tag-angles-discarded): stock
     * computes these tag angles and applies the weapon roll adjust, but the
     * subsequent G_SetAngle call still uses source->currentAngles. */
    tagAngles[2] += ITEM_WEAPON_ANGLE_ADJUST;
    (void)tagAngles[2];

    G_SetAngle(dropped, source->currentAngles);

    dropped->apos.trType =
        TR_LINEAR;
    dropped->apos.trTime = level.time;
    /* randSigned * const, one float store per component -> shim. */
#if EMULATE_X87
    dropped->apos.trDelta[0] = x87f_store_f32(x87f_mul(
        game_compat_item_random_signed_x87(), x87f_load_f32(DROPPED_WEAPON_TAG_YAW_RANDOM)));
    dropped->apos.trDelta[1] = x87f_store_f32(x87f_mul(
        game_compat_item_random_signed_x87(), x87f_load_f32(DROPPED_WEAPON_TAG_PITCH_RANDOM)));
    dropped->apos.trDelta[2] = x87f_store_f32(x87f_mul(
        game_compat_item_random_signed_x87(), x87f_load_f32(DROPPED_WEAPON_TAG_ROLL_RANDOM)));
#else
    dropped->apos.trDelta[0] =
        game_compat_item_random_signed() * DROPPED_WEAPON_TAG_YAW_RANDOM;
    dropped->apos.trDelta[1] =
        game_compat_item_random_signed() * DROPPED_WEAPON_TAG_PITCH_RANDOM;
    dropped->apos.trDelta[2] =
        game_compat_item_random_signed() * DROPPED_WEAPON_TAG_ROLL_RANDOM;
#endif
}

/* VERIFIED_DECOMPILER(0x552e6, 652e6_Drop_Weapon.c, VERIFY-P1-ITEMS-2026-06-17): DATAFLOW_VERIFIED - ownership weapon check, dropDisabled/clipRequired branches, ammo/clip transfer or synthetic ammo, dropped counts, tag-placement helper behavior, and return checked against current decompiler output. */
gentity_t *Drop_Weapon(gentity_t *ent, int weapon, const char *tagName)
{
    gclient_t *client = ent->client;
    int ammoIndex;
    int clipIndex;
    int ammoCount;
    int clipCount;
    gentity_t *dropped;

    if (client != 0 && Com_BitCheck(client->ps.weaponBits, weapon) == 0) {
        BG_TakePlayerWeapon(&client->ps, weapon);
        return 0;
    }

    ammoIndex = BG_AmmoForWeapon(weapon);
    clipIndex = BG_ClipForWeapon(weapon);

    if (((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->dropDisabled != 0) {
        return 0;
    }

    if (((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->clipRequired != 0 &&
        client->ps.clips[clipIndex] == 0) {
        BG_TakePlayerWeapon(&client->ps, weapon);
        return 0;
    }

    dropped = Drop_Item(ent, game_compat_item_def_for_index(weapon), 0.0f, 0);

    if (client == 0) {
        game_compat_drop_weapon_synthetic_ammo(weapon, clipIndex, &ammoCount, &clipCount);
    } else {
        ammoCount = client->ps.ammo[ammoIndex];
        client->ps.ammo[ammoIndex] = 0;
        clipCount = client->ps.clips[clipIndex];
        client->ps.clips[clipIndex] = 0;
        BG_TakePlayerWeapon(&client->ps, weapon);
    }

    dropped->itemCount = ammoCount;
    dropped->droppedClipCount = clipCount;

    if (dropped->itemCount == 0) {
        dropped->itemCount = -1;
    }
    if (dropped->droppedClipCount == 0) {
        dropped->droppedClipCount = -1;
    }

    if (tagName != 0) {
        game_compat_drop_weapon_place_at_tag(ent, dropped, tagName);
    }

    return dropped;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Pickup_Weapon generated body. */
static int game_compat_pickup_weapon_random_count(int weapon)
{
    int minAmmo = ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->dropAmmoMin;
    int maxAmmo = ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->dropAmmoMax;

    if (maxAmmo < minAmmo) {
        int oldMin = minAmmo;

        minAmmo = maxAmmo;
        maxAmmo = oldMin;
    }

    if (maxAmmo == 0 && minAmmo == 0) {
        int clipIndex = BG_ClipForWeapon(weapon);
        int clipSize = BG_GetAmmoClipSize(clipIndex);
        /* Original i386 Pickup_Weapon 0x53991..0x539de keeps rand()/2^31 in
         * x87, rounds (v + 1.0) * 0.5 once to float, and multiplies the raw
         * integer clipSize-1 in 80-bit; same shape as game_compat_drop_weapon_default_ammo. */
#if EMULATE_X87
        float ammoScale = x87f_store_f32(x87f_mul(
            x87f_add(game_compat_item_random_half_open_extended_x87(), x87f_load_f32(1.0f)),
            x87f_load_f32(0.5f)));

        return Game_RoundFloatPlusHalf(x87f_store_f32(x87f_mul(
                   x87f_load_i32(clipSize - 1), x87f_load_f32(ammoScale)))) +
               1;
#else
        float ammoScale =
            (float)((game_compat_item_random_half_open_extended() + 1.0L) * 0.5L);

        return Game_RoundFloatPlusHalf(
                   (float)((long double)(clipSize - 1) *
                           (long double)ammoScale)) +
               1;
#endif
    }

    if (maxAmmo < 0) {
        return 0;
    }

    if (maxAmmo == minAmmo) {
        return minAmmo;
    }

    return minAmmo + rand() % (maxAmmo - minAmmo);
}

/* VERIFIED_DECOMPILER(0x533b8, 633b8_Fill_Clip.c, VERIFY-P1-ITEMS2-2026-06-17): DATAFLOW_VERIFIED - weapon bounds guard, ammo/clip refill amount, and conditional stores checked against current decompiler output. */
void Fill_Clip(gclient_t *client, int weapon)
{
    int ammoIndex = BG_AmmoForWeapon(weapon);
    int clipIndex = BG_ClipForWeapon(weapon);
    int refill;

    if (weapon <= 0 || weapon > BG_GetNumWeapons()) {
        return;
    }

    refill = BG_GetAmmoClipSize(clipIndex) - client->ps.clips[clipIndex];
    if (client->ps.ammo[ammoIndex] < refill) {
        refill = client->ps.ammo[ammoIndex];
    }

    if (refill != 0) {
        client->ps.ammo[ammoIndex] -= refill;
        client->ps.clips[clipIndex] += refill;
    }
}

/* VERIFIED_DECOMPILER(0x53495, 63495_Add_Ammo.c, VERIFY-P1-ITEMS2-2026-06-17): DATAFLOW_VERIFIED - ammo addition, clip-only branch, fill-clip call, max clamps, pickup limit overflow handling, weapon removal, and return delta checked against current decompiler output. */
int Add_Ammo(gentity_t *ent, int weapon, int amount, qboolean fillClip)
{
    gclient_t *client = ent->client;
    int ammoIndex = BG_AmmoForWeapon(weapon);
    int clipIndex = BG_ClipForWeapon(weapon);
    int oldAmmo = client->ps.ammo[ammoIndex];
    int oldClip = client->ps.clips[clipIndex];
    qboolean clipOnly;

    client->ps.ammo[ammoIndex] += amount;
    clipOnly = BG_WeaponIsClipOnly(weapon) != 0;

    if (clipOnly) {
        BG_GivePlayerWeapon(&client->ps, weapon);
    }

    if (fillClip || clipOnly) {
        Fill_Clip(client, weapon);
    }

    if (clipOnly) {
        client->ps.ammo[ammoIndex] = 0;
    } else {
        int maxAmmo = BG_GetAmmoTypeMax(ammoIndex);

        if (maxAmmo < client->ps.ammo[ammoIndex]) {
            client->ps.ammo[ammoIndex] = BG_GetAmmoTypeMax(ammoIndex);
        }
    }

    if (BG_GetAmmoClipSize(clipIndex) < client->ps.clips[clipIndex]) {
        client->ps.clips[clipIndex] = BG_GetAmmoClipSize(clipIndex);
    }

    if (((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->sharedAmmoCapIndex >= 0) {
        int overflow = BG_GetMaxPickupableAmmo(client, weapon);

        if (overflow < 0) {
            if (!clipOnly) {
                client->ps.ammo[ammoIndex] += overflow;
                if (client->ps.ammo[ammoIndex] < 0) {
                    client->ps.ammo[ammoIndex] = 0;
                }
            } else {
                client->ps.clips[clipIndex] += overflow;
                if (client->ps.clips[clipIndex] < 1) {
                    client->ps.clips[clipIndex] = 0;
                    BG_TakePlayerWeapon(&client->ps, weapon);
                    return 0;
                }
            }
        }
    }

    return (client->ps.ammo[ammoIndex] - oldAmmo) +
           (client->ps.clips[clipIndex] - oldClip);
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Pickup_Weapon generated body. */
static int game_compat_pickup_weapon_reserve_count(gentity_t *itemEnt, int weapon)
{
    int count = itemEnt->itemCount;
    int ammoIndex;
    int maxAmmo;

    if (count < 0) {
        return 0;
    }

    if (count == 0) {
        count = game_compat_pickup_weapon_random_count(weapon);
        itemEnt->itemCount = count;
        if (itemEnt->itemCount < 1) {
            itemEnt->itemCount = 0;
        }
    }

    ammoIndex = BG_AmmoForWeapon(weapon);
    maxAmmo = BG_GetAmmoTypeMax(ammoIndex);
    if (maxAmmo < itemEnt->itemCount) {
        itemEnt->itemCount = maxAmmo;
    }

    return itemEnt->itemCount;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Pickup_Weapon generated body. */
static int game_compat_pickup_weapon_clip_count(gentity_t *itemEnt, int weapon, int *reserveCount)
{
    int clipCount = itemEnt->droppedClipCount;
    int clipIndex;
    int clipSize;

    if (clipCount < 0) {
        return 0;
    }

    if (clipCount == 0) {
        if (itemEnt->itemCount < 0) {
            itemEnt->droppedClipCount = 0;
        } else {
            clipIndex = BG_ClipForWeapon(weapon);
            clipSize = BG_GetAmmoClipSize(clipIndex);
            itemEnt->droppedClipCount = clipSize;
            if (itemEnt->itemCount < itemEnt->droppedClipCount) {
                itemEnt->droppedClipCount = itemEnt->itemCount;
            }
            itemEnt->itemCount -= itemEnt->droppedClipCount;
            *reserveCount = itemEnt->itemCount;
        }
    }

    clipIndex = BG_ClipForWeapon(weapon);
    clipSize = BG_GetAmmoClipSize(clipIndex);
    if (clipSize < itemEnt->droppedClipCount) {
        itemEnt->droppedClipCount = clipSize;
    }

    return itemEnt->droppedClipCount;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Pickup_Weapon generated body. */
static void game_compat_pickup_weapon_trace_dropped_replacement(gentity_t *pickupItem,
                                                gentity_t *dropped)
{
    if ((pickupItem->flags & DROPPED_ITEM_FLAGS) == 0) {
        trace_t trace;
        vec3_t end;

        end[0] = dropped->currentOrigin[0];
        end[1] = dropped->currentOrigin[1];
        end[2] = dropped->currentOrigin[2] - WEAPON_REDROP_TRACE_DISTANCE;

        trap_Trace(&trace, dropped->currentOrigin,
                   dropped->mins,
                   dropped->maxs,
                   end, dropped->s_number,
                   dropped->clipmask);

        if (trace.fraction < 1.0f) {
            G_SetOrigin(dropped, trace.endpos);
            trap_LinkEntity(dropped);
        }
    } else {
        G_SetOrigin(dropped, pickupItem->currentOrigin);
        G_SetAngle(dropped, pickupItem->currentAngles);
        trap_LinkEntity(dropped);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Pickup_Weapon generated body. */
static gentity_t *game_compat_pickup_weapon_drop_for_slot(gentity_t *player, int weaponSlot)
{
    int weapon = (int)(int8_t)player->client->ps.weaponSlots[weaponSlot];

    return Drop_Weapon(player, weapon, 0);
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Pickup_Weapon generated body. */
static int game_compat_pickup_weapon_drop_replacement(gentity_t *itemEnt, gentity_t *player,
                                       int weapon, const weaponInfo_t *weaponInfo,
                                       gentity_t **dropped)
{
    gclient_t *client = player->client;
    int currentWeapon = client->ps.currentWeapon;
    int slot = BG_GetStackSlotForWeapon(
        client, weapon, ((const weaponInfo_t *)BG_GetInfoForWeapon(currentWeapon))->slot);

    if (slot != 0) {
        *dropped = 0;
        return 1;
    }

    if (weaponInfo->slot ==
        ((const weaponInfo_t *)BG_GetInfoForWeapon(currentWeapon))->slot) {
        *dropped = Drop_Weapon(player, currentWeapon, 0);
    } else {
        int weaponSlot = weaponInfo->slot;

        if (weaponSlot >= WEAPSLOT_PISTOL &&
            weaponSlot <= WEAPSLOT_LAST_DROPPABLE) {
            *dropped = game_compat_pickup_weapon_drop_for_slot(player, weaponSlot);
        } else {
            for (weaponSlot = WEAPSLOT_PRIMARY_FIRST;
                 weaponSlot < WEAPSLOT_PRIMARY_LIMIT; weaponSlot++) {
                /* ORIGINAL_BINARY_BUG(game-pickup-weapon-primary-replacement-ammo-source):
                 * stock loads the weapon assigned to this slot for Drop_Weapon,
                 * but tests the incoming weapon's ammo/clip pools when deciding
                 * whether this occupied slot may be replaced. */
                int ammoIndex = BG_AmmoForWeapon(weapon);
                int clipIndex = BG_ClipForWeapon(weapon);

                if (client->ps.ammo[ammoIndex] == 0 && client->ps.clips[clipIndex] == 0) {
                    *dropped = game_compat_pickup_weapon_drop_for_slot(player, weaponSlot);
                    break;
                }
            }

            if (weaponSlot >= WEAPSLOT_PRIMARY_LIMIT) {
                trap_SendServerCommand((uint32_t)(int)(player - g_entities), 0,
                                       "f \"GAME_CANT_GET_PRIMARY_WEAP_MESSAGE\"");
                return 0;
            }
        }
    }

    if (*dropped == 0) {
        if (BG_GetEmptySlotForWeapon(client, weapon) == 0) {
            return 0;
        }
    } else {
        game_compat_pickup_weapon_trace_dropped_replacement(itemEnt, *dropped);
    }

    return 1;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Pickup_Weapon generated body. */
static int game_compat_pickup_weapon_grant_new_weapon(gentity_t *itemEnt, gentity_t *player,
                                      int weapon, const weaponInfo_t *weaponInfo,
                                      int traceMode, gentity_t **dropped)
{
    gclient_t *client = player->client;

    *dropped = 0;

    if (client->ps.currentWeapon != 0) {
        if (Com_BitCheck(client->ps.weaponBits, client->ps.currentWeapon) == 0) {
            return 0;
        }

        if (BG_IsPlayerWeaponInSlot(&client->ps, client->ps.currentWeapon, 1) == 0) {
            int currentSlot = ((const weaponInfo_t *)BG_GetInfoForWeapon(client->ps.currentWeapon))->slot;

            if (BG_GetStackSlotForWeapon(client, client->ps.currentWeapon,
                                         currentSlot) == 0 &&
                BG_GetEmptySlotForWeapon(client, weapon) == 0) {
                Com_Printf("WARNING: cannot swap out a debug weapon "
                           "(can result from too many weapons given to the player)\n");
                return 0;
            }
        }
    }

    if (BG_GetEmptySlotForWeapon(client, weapon) == 0) {
        if (game_compat_pickup_weapon_drop_replacement(itemEnt, player, weapon, weaponInfo,
                                        dropped) == 0) {
            return 0;
        }
    }

    BG_GivePlayerWeapon(&client->ps, weapon);
    if (traceMode == 0) {
        trap_SendServerCommand((uint32_t)(int)(player - g_entities), 1, va("a %i", weapon));
    }

    return 1;
}

/* VERIFIED_DECOMPILER(0x538e7, 638e7_FUN_000638e7.c, VERIFY-P1-ITEMS2-2026-06-17): DATAFLOW_VERIFIED - weapon grant/drop helpers, ammo/clip transfer, server messages, script notify arguments, pool handling, and respawn return checked against current decompiler output. */
int Pickup_Weapon(gentity_t *itemEnt, gentity_t *player, int *event,
                  int traceMode)
{
    int weapon = ((const gitem_t *)itemEnt->itemInfo)->weapon;
    const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(weapon);
    int reserveCount = game_compat_pickup_weapon_reserve_count(itemEnt, weapon);
    int clipCount = game_compat_pickup_weapon_clip_count(itemEnt, weapon, &reserveCount);
    int alreadyOwned = Com_BitCheck(player->client->ps.weaponBits, weapon);
    gentity_t *dropped = 0;

    if (alreadyOwned == 0) {
        if (game_compat_pickup_weapon_grant_new_weapon(itemEnt, player, weapon, weaponInfo,
                                       traceMode, &dropped) == 0) {
            return 0;
        }
    }

    if (alreadyOwned == 0) {
        if (clipCount >= 0) {
            int clipIndex = BG_ClipForWeapon(weapon);
            int clipSize = BG_GetAmmoClipSize(clipIndex);

            if (clipSize < clipCount) {
                reserveCount += clipCount - clipSize;
                clipCount = clipSize;
            }

            player->client->ps.clips[clipIndex] = clipCount;
        }

        Add_Ammo(player, weapon, reserveCount, clipCount == -1);
    } else {
        int added;

        *event = PICKUP_EVENT_EXISTING_WEAPON_AMMO;
        reserveCount += clipCount;
        added = Add_Ammo(player, weapon, reserveCount, 0);

        if (added != 0) {
            if (BG_WeaponIsClipOnly(weapon) == 0) {
                trap_SendServerCommand((uint32_t)(int)(player - g_entities), 0,
                                       va("f \"GAME_PICKUP_AMMO\x14%s\"",
                                          weaponInfo->displayName));
            } else {
                trap_SendServerCommand((uint32_t)(int)(player - g_entities), 0,
                                       va("f \"GAME_PICKUP_CLIPONLY_AMMO\x14%s\"",
                                          weaponInfo->displayName));
            }
        }

        if (added != reserveCount) {
            itemEnt->itemCount -= added;
            if (itemEnt->itemCount < 1) {
                itemEnt->droppedClipCount += itemEnt->itemCount;
                itemEnt->itemCount = -1;
                if (itemEnt->droppedClipCount < 1) {
                    itemEnt->droppedClipCount = -1;
                }
            }

            if ((itemEnt->itemCount > 0 ||
                 itemEnt->droppedClipCount > 0) &&
                g_weaponAmmoPools.integer != 0) {
                return 0;
            }
        }
    }

    if (dropped == 0) {
        Scr_AddUndefined();
    } else {
        Scr_AddEntity(dropped);
    }
    Scr_AddEntity(player);
    Scr_Notify(itemEnt, scr_const_trigger, 2);

    if ((itemEnt->spawnflags &
         ITEM_SPAWNFLAG_WEAPON_RESPAWN) == 0) {
        return -1;
    }

    return g_weaponRespawn.integer;
}

/* VERIFIED_DECOMPILER(0x5644a, 6644a_G_BounceItem.c, VERIFY-P1-ITEMS2-2026-06-17): DATAFLOW_VERIFIED - bounce time, trajectory delta reflection, startsolid retrace, settle-vs-bounce branch, ground entity, angles, and link side effect checked against current decompiler output. */
void G_BounceItem(gentity_t *ent, trace_t *trace)
{
    /* Stock 0x56481: (fild)(time-previousTime) * fraction, fistp-direct trunc. */
#if EMULATE_X87
    int bounceTime =
        x87f_store_i32_trunc(x87f_mul(
            x87f_load_i32(level.time - level.previousTime),
            x87f_load_f32(trace->fraction))) +
        level.previousTime;
#else
    int bounceTime =
        (int32_t)((long double)(level.time - level.previousTime) *
                  (long double)trace->fraction) +
        level.previousTime;
#endif
    vec3_t velocity;
    float dot;
    float *delta = ent->pos.trDelta;

    BG_EvaluateTrajectoryDelta(&ent->pos, bounceTime, velocity);

    /* Original i386 G_BounceItem 0x564ef..0x5657f stores the reflected
     * delta to float first (dot * -2.0f * normal + velocity), then scales
     * by bounceFactor as a second float store per component.  The velocity.normal
     * dot (0x564d0) is 3-mul/2-add kept 80-bit, stored to float. */
#if EMULATE_X87
    dot = x87f_store_f32(x87f_add(x87f_add(
        x87f_mul(x87f_load_f32(velocity[0]), x87f_load_f32(trace->normal[0])),
        x87f_mul(x87f_load_f32(velocity[1]), x87f_load_f32(trace->normal[1]))),
        x87f_mul(x87f_load_f32(velocity[2]), x87f_load_f32(trace->normal[2]))));
    for (int k = 0; k < 3; k++) {
        delta[k] = x87f_store_f32(x87f_add(
            x87f_mul(x87f_mul(x87f_load_f32(dot), x87f_load_f32(-2.0f)),
                     x87f_load_f32(trace->normal[k])),
            x87f_load_f32(velocity[k])));
    }
#else
    dot = velocity[0] * trace->normal[0] +
          velocity[1] * trace->normal[1] +
          velocity[2] * trace->normal[2];
    delta[0] = dot * -2.0f * trace->normal[0] + velocity[0];
    delta[1] = dot * -2.0f * trace->normal[1] + velocity[1];
    delta[2] = dot * -2.0f * trace->normal[2] + velocity[2];
#endif
    delta[0] *= ent->bounceFactor;
    delta[1] *= ent->bounceFactor;
    delta[2] *= ent->bounceFactor;

    if (trace->startsolid != 0) {
        vec3_t end;

        delta[0] = 0.0f;
        delta[1] = 0.0f;
        delta[2] = 0.0f;
        end[0] = ent->currentOrigin[0];
        end[1] = ent->currentOrigin[1];
        end[2] = ent->currentOrigin[2] - ITEM_BOUNCE_RETRACE_Z;

        if ((ent->s_flags & EF_CAPSULE) == 0) {
            trap_Trace(trace, ent->currentOrigin,
                       ent->mins,
                       ent->maxs,
                       end, ent->s_number, ITEM_TRACE_CONTENTS);
        } else {
            trap_TraceCapsule(trace, ent->currentOrigin,
                              ent->mins,
                              ent->maxs,
                              end, ent->s_number, ITEM_TRACE_CONTENTS);
        }
    }

    /* Original i386 0x56676..0x56689 loads normal.z, loads 0.0f, then FXCH
     * makes FUCOMPP compare normal.z against zero; JA therefore requires an
     * ordered normal.z > 0.0f.  The second JA requires 40.0f > delta.z. */
    if (!(trace->normal[2] > 0.0f && delta[2] < ITEM_BOUNCE_STOP_Z)) {
        ent->currentOrigin[0] += trace->normal[0];
        ent->currentOrigin[1] += trace->normal[1];
        ent->currentOrigin[2] += trace->normal[2];
        ent->pos.trBase[0] =
            ent->currentOrigin[0];
        ent->pos.trBase[1] =
            ent->currentOrigin[1];
        ent->pos.trBase[2] =
            ent->currentOrigin[2];
        ent->pos.trTime = level.time;
    } else {
        float axis[3][3];
        vec3_t angles;
        int itemIndex = ent->itemIndex;

        trace->endpos[2] += game_compat_item_random_settled_height();
        G_SetOrigin(ent, trace->endpos);
        ent->groundEntityNum = trace->entityNum;

        axis[2][0] = trace->normal[0];
        axis[2][1] = trace->normal[1];
        axis[2][2] = trace->normal[2];
        AngleVectors(ent->currentAngles, axis[0], 0, 0);
        CrossProduct(axis[2], axis[0], axis[1]);
        CrossProduct(axis[1], axis[2], axis[0]);
        AxisToAngles(&axis[0][0], angles);

        if (game_compat_item_def_for_index(itemIndex)->type == IT_WEAPON) {
            angles[2] += ITEM_WEAPON_ANGLE_ADJUST;
        }

        G_SetAngle(ent, angles);
        trap_LinkEntity(ent);
    }
}

/* VERIFIED_DECOMPILER(0x56876, 66876_G_RunItem.c, VERIFY-P1-ITEMS2-2026-06-17): DATAFLOW_VERIFIED - original gravity/runthink/trace/link/bounce/free state changes checked against current decompiler output. */
void G_RunItem(gentity_t *ent)
{
    if (ent->groundEntityNum == ENTITYNUM_NONE &&
        ent->pos.trType !=
            TR_GRAVITY) {
        ent->pos.trType =
            TR_GRAVITY;
        ent->pos.trTime = level.time;
    }

    if (ent->pos.trType ==
            TR_STATIONARY ||
        ent->pos.trType ==
            TR_LINKED) {
        G_RunThink(ent);
    } else {
        trace_t trace;
        vec3_t nextOrigin;
        int clipmask = ent->clipmask;

        BG_EvaluateTrajectory(&ent->pos, level.time, nextOrigin);
        if (clipmask == 0) {
            clipmask = ITEM_DEFAULT_CLIPMASK;
        }

        if ((ent->s_flags & EF_CAPSULE) == 0) {
            trap_Trace(&trace, ent->currentOrigin,
                       ent->mins,
                       ent->maxs,
                       nextOrigin,
                       ent->passEntityNum,
                       clipmask);
        } else {
            trap_TraceCapsule(&trace, ent->currentOrigin,
                              ent->mins,
                              ent->maxs,
                              nextOrigin,
                              ent->passEntityNum,
                              clipmask);
        }

        ent->currentOrigin[0] = trace.endpos[0];
        ent->currentOrigin[1] = trace.endpos[1];
        ent->currentOrigin[2] = trace.endpos[2];

        if (trace.startsolid != 0) {
            trace.fraction = 0.0f;
        }

        trap_LinkEntity(ent);
        G_RunThink(ent);

        if (ent->linked != 0 && trace.fraction != 1.0f) {
            int contents = trap_PointContents(ent->currentOrigin,
                                              PASS_ENTITY_NONE,
                                              ITEM_POINT_CONTENTS_MASK);

            if (contents == 0) {
                G_BounceItem(ent, &trace);
            } else {
                G_FreeEntity(ent);
            }
        }
    }
}

/* VERIFIED_DECOMPILER(0x56146, 66146_G_SpawnItem.c, VERIFY-P1-ITEMS2-2026-06-17): DATAFLOW_VERIFIED - spawn vars, registration, model/DObj setup, bounds, flags, callbacks, immediate/delayed link paths, and debug-only diagnostics checked against current decompiler output. */
void G_SpawnItem(gentity_t *ent, gitem_t *item)
{
    int itemIndex = game_compat_item_index_from_def(item);
    itemType_t itemType = game_compat_item_def_for_index(itemIndex)->type;
    const char *noiseAlias;

    G_SpawnFloat("random", "0", &ent->itemRandom);
    G_SpawnFloat("wait", "0", &ent->itemWait);
    RegisterItem(itemIndex, 0);

    ent->itemInfo = item;
    G_SetModel(ent, game_compat_item_def_for_index(itemIndex)->worldModel);

    if (G_SpawnString("noise", 0, &noiseAlias) != 0) {
        ent->itemSoundAlias =
            G_SoundAliasIndex(noiseAlias);
    }

    ent->bounceFactor = 0;
    game_compat_set_item_bounds(ent, itemType);

    ent->svFlags |= SVF_CAPSULE;
    ent->s_flags |= EF_CAPSULE;
    ent->scriptContents = ITEM_SPAWN_PACKED_STATE;
    ent->touch = Touch_Item_Auto;
    ent->s_eType = ET_ITEM;
    ent->itemIndex = itemIndex;
    G_DObjUpdate(ent);

    ent->clientNum = ENTITYNUM_WORLD;
    ent->use = Use_Item;
    ent->flags |= FL_SUPPORTS_LINKTO;

    if (level.spawningMapEntities == 0) {
        if ((ent->spawnflags &
             ITEM_SPAWNFLAG_NO_DROP_TO_FLOOR) == 0) {
            ent->groundEntityNum = ENTITYNUM_NONE;
            if (itemType == IT_WEAPON) {
                ent->currentAngles[2] += ITEM_WEAPON_ANGLE_ADJUST;
            }
        }

        G_SetAngle(ent, ent->currentAngles);
        G_SetOrigin(ent, ent->currentOrigin);
        trap_LinkEntity(ent);
    } else {
        G_SetAngle(ent, ent->currentAngles);
        ent->nextthink = level.time + ITEM_DELAYED_SPAWN_MS;
        ent->think = FinishSpawningItem;
    }
}

/* VERIFIED_DECOMPILER(0x53772, 63772_FUN_00063772.c, VERIFY-P1-ITEMS2-2026-06-17): DATAFLOW_VERIFIED - default count, Add_Ammo call, clip-only message branch, remaining pool update, and respawn return checked against current decompiler output. */
int Pickup_Ammo(gentity_t *itemEnt, gentity_t *player)
{
    const gitem_t *itemInfo = itemEnt->itemInfo;
    int weapon = itemInfo->weapon;
    int count = itemEnt->itemCount;
    int added;

    if (count == 0) {
        count = itemInfo->quantity;
    }

    added = Add_Ammo(player, weapon, count, 0);
    if (added == 0) {
        return 0;
    }

    if (BG_WeaponIsClipOnly(weapon) != 0) {
        trap_SendServerCommand((uint32_t)(int)(player - g_entities), 0,
                               va("f \"GAME_PICKUP_CLIPONLY_AMMO\x14%s\"",
                                  ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->displayName));
    } else {
        trap_SendServerCommand((uint32_t)(int)(player - g_entities), 0,
                               va("f \"GAME_PICKUP_AMMO\x14%s\"",
                                  ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->displayName));
    }

    Scr_AddEntity(player);
    Scr_Notify(itemEnt, scr_const_trigger, 1);
    return PICKUP_AMMO_RESPAWN_TIME;
}

/* VERIFIED_DECOMPILER(0x54361, 64361_Pickup_Health.c, VERIFY-WAVE3-ITEM-TOUCH-VISIBILITY-2026-06-17): DATAFLOW_VERIFIED - amount selection, overheal limit, percent rounding repair, health mirrors, pickup message, script notify, and no-respawn return checked against current decompiler output. */
int Pickup_Health(gentity_t *itemEnt, gentity_t *player)
{
    const gitem_t *itemInfo = itemEnt->itemInfo;
    int itemQuantity = itemInfo->quantity;
    int maxHealth = player->client->ps.maxHealth;
    int healthLimit = maxHealth;
    int amount;
    int oldHealth;

    if (itemQuantity == PICKUP_HEALTH_SMALL ||
        itemQuantity == PICKUP_HEALTH_LARGE) {
        healthLimit = maxHealth * 2;
    }

    amount = itemEnt->itemCount;
    if (amount == 0) {
        amount = itemQuantity;
    }

    oldHealth = player->health;
    /* Stock 0x543fc: fild amount * fild maxHealth * 0.01f(DWORD), fistp trunc. */
#if EMULATE_X87
    player->health += x87f_store_i32_trunc(x87f_mul(
        x87f_mul(x87f_load_i32(amount), x87f_load_i32(maxHealth)),
        x87f_load_f32(0.01f)));
#else
    player->health += (int32_t)((long double)amount *
                                (long double)maxHealth *
                                (long double)0.01f);
#endif

    if (healthLimit < player->health) {
        player->health = healthLimit;
    } else {
        /* Stock 0x5447b/0x544cf: (int)(health*100) fild / fild maxHealth, fistp
         * trunc.  The *100 is integer arithmetic before the fild. */
#if EMULATE_X87
        int currentPercent = x87f_store_i32_trunc(x87f_div(
            x87f_load_i32(player->health * 100), x87f_load_i32(maxHealth)));
        int oldPercent = x87f_store_i32_trunc(x87f_div(
            x87f_load_i32(oldHealth * 100), x87f_load_i32(maxHealth)));
#else
        int currentPercent =
            (int32_t)((long double)(player->health * 100) /
                      (long double)maxHealth);
        int oldPercent =
            (int32_t)((long double)(oldHealth * 100) /
                      (long double)maxHealth);
#endif

        if (currentPercent < 1) {
            currentPercent = 1;
        } else if (currentPercent > 100) {
            currentPercent = 100;
        }

        if (oldPercent < 1) {
            oldPercent = 1;
        }

        oldPercent += amount;
        if (oldPercent > 100) {
            oldPercent = 100;
        }

        if (currentPercent != oldPercent) {
            player->health = (oldPercent * maxHealth) / 100;
        }
    }

    player->client->ps.health = player->health;
    trap_SendServerCommand((uint32_t)(int)(player - g_entities), 0,
                           va("f \"GAME_PICKUP_HEALTH\x15%i\"", amount));
    Scr_AddEntity(player);
    Scr_Notify(itemEnt, scr_const_trigger, 1);
    return PICKUP_HEALTH_NO_RESPAWN;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Touch_Item generated body. */
static void game_compat_touch_item_send_cant_grab_weapon_message(gentity_t *itemEnt,
                                               gentity_t *player)
{
    int weapon = ((const gitem_t *)itemEnt->itemInfo)->weapon;

    if (Com_BitCheck(player->client->ps.weaponBits, weapon) == 0) {
        switch (((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->slot) {
        case WEAPSLOT_PRIMARY_FIRST:
        case WEAPSLOT_PRIMARY_SECOND:
            trap_SendServerCommand(
                (uint32_t)(int)(player - g_entities), 0,
                va("f \"GAME_CANT_GET_PRIMARY_WEAP_MESSAGE\""));
            break;
        case WEAPSLOT_PISTOL:
            trap_SendServerCommand(
                (uint32_t)(int)(player - g_entities), 0,
                va("f \"GAME_CANT_GET_PISTOL_WEAP_MESSAGE\""));
            break;
        case WEAPSLOT_GRENADE:
            trap_SendServerCommand(
                (uint32_t)(int)(player - g_entities), 0,
                va("f \"GAME_CANT_GET_GRENADE_WEAP_MESSAGE\""));
            break;
        case WEAPSLOT_SMOKER:
            trap_SendServerCommand(
                (uint32_t)(int)(player - g_entities), 0,
                va("f \"GAME_CANT_GET_SMOKER_WEAP_MESSAGE\""));
            break;
        default:
            break;
        }
    } else {
        trap_SendServerCommand(
            (uint32_t)(int)(player - g_entities), 0,
            va("f \"GAME_PICKUP_CANTCARRYMOREAMMO\x14%s\"",
               ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->displayName));
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Touch_Item generated body. */
static void game_compat_touch_item_log_pickup(gentity_t *itemEnt, gentity_t *player)
{
    const gitem_t *itemInfo = itemEnt->itemInfo;
    char cleanName[CLIENT_CLEAN_NAME_LEN];
    int guid;

    Q_strncpyz(cleanName,
               player->client->userInfoName,
               sizeof(cleanName));
    Q_CleanStr(cleanName);

    guid = trap_GetGuid(player->s_number);
    if (itemInfo->type == IT_WEAPON) {
        int weapon = itemInfo->weapon;

        G_LogPrintf("Weapon;%d;%d;%s;%s\n", guid, player->s_number,
                    cleanName,
                    ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->pickupName);
    } else {
        G_LogPrintf("Item;%d;%d;%s;%s\n", guid, player->s_number,
                    cleanName, itemInfo->classname);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Touch_Item generated body. */
static int game_compat_touch_item_dispatch_pickup(gentity_t *itemEnt, gentity_t *player,
                                   int *event, int traceMode)
{
    switch (((const gitem_t *)itemEnt->itemInfo)->type) {
    case IT_WEAPON:
        return Pickup_Weapon(itemEnt, player, event, traceMode);
    case IT_AMMO:
        return Pickup_Ammo(itemEnt, player);
    case IT_HEALTH:
        return Pickup_Health(itemEnt, player);
    default:
        return 0;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from Touch_Item generated body. */
static void game_compat_touch_item_schedule_respawn(gentity_t *itemEnt, int respawnSeconds)
{
    float wait = itemEnt->itemWait;
    float random = itemEnt->itemRandom;

    if (wait == PICKUP_HEALTH_NO_RESPAWN) {
        itemEnt->flags |= FL_NOCLIENT;
        itemEnt->s_flags |= EF_NODRAW;
        itemEnt->scriptContents = 0;
        itemEnt->unlinkOnTimeout = 1;
        return;
    }

    if (wait != 0.0f) {
#if EMULATE_X87
        respawnSeconds =
            x87f_store_i32_trunc(x87f_load_f32(wait));
#elif defined(__x86_64__)
        respawnSeconds =
            CODUO_X87_TRUNCATE_I32((long double)wait);
#else
        respawnSeconds = (int32_t)wait;
#endif
    }

    if (random != 0.0f) {
#if EMULATE_X87
        respawnSeconds += x87f_store_i32_trunc(
            x87f_mul(game_compat_item_random_signed_x87(), x87f_load_f32(random)));
#elif defined(__x86_64__)
        respawnSeconds += CODUO_X87_TRUNCATE_I32(
            (long double)game_compat_item_random_signed() *
            (long double)random);
#else
        respawnSeconds += (int32_t)(game_compat_item_random_signed() * random);
#endif
        if (respawnSeconds < 1) {
            respawnSeconds = 1;
        }
    }

    if ((itemEnt->flags & DROPPED_ITEM_FLAGS) != 0) {
        itemEnt->skipTypeDispatch = 1;
    }

    itemEnt->svFlags |= ITEM_SVF_RESPAWNING;
    itemEnt->flags |= FL_NOCLIENT;
    itemEnt->scriptContents = 0;

    if (respawnSeconds < 1) {
        itemEnt->nextthink = 0;
        itemEnt->think = 0;
    } else {
        itemEnt->nextthink = level.time + respawnSeconds * 1000;
        itemEnt->think = RespawnItem;
    }

    if ((itemEnt->flags & DROPPED_ITEM_FLAGS) != 0) {
        itemEnt->think = G_FreeEntity;
        itemEnt->nextthink =
            level.time + DROPPED_ITEM_PICKUP_FREE_DELAY_MS;
    }

    trap_LinkEntity(itemEnt);
}

/* VERIFIED_DECOMPILER(0x5473b, 6473b_Touch_Item.c, VERIFY-WAVE3-ITEM-TOUCH-VISIBILITY-2026-06-17): DATAFLOW_VERIFIED - auto-touch gate, manual denial messages, userInfoName logging, pickup dispatch, event selection, visibility/no-respawn stores, dropped-item free path, and link behavior checked against current decompiler output. */
void Touch_Item(gentity_t *itemEnt, gentity_t *player, int traceMode)
{
    int event = EV_ITEM_PICKUP;
    int respawnSeconds;

    if (*game_compat_item_auto_touch_flag(itemEnt) == 0) {
        return;
    }

    *game_compat_item_auto_touch_flag(itemEnt) = 0;

    if (player->client == 0 || player->health <= 0) {
        return;
    }

    if (BG_CanItemBeGrabbed(itemEnt, player->client, traceMode) == 0 &&
        traceMode != ITEM_TOUCH_AUTOGRAB) {
        if (traceMode == ITEM_TOUCH_MANUAL &&
            itemEnt->clientNum != player->s_number &&
            ((const gitem_t *)itemEnt->itemInfo)->type == IT_WEAPON) {
            game_compat_touch_item_send_cant_grab_weapon_message(itemEnt, player);
        }
        return;
    }

    game_compat_touch_item_log_pickup(itemEnt, player);

    respawnSeconds = game_compat_touch_item_dispatch_pickup(itemEnt, player, &event, traceMode);
    if (respawnSeconds == 0) {
        return;
    }

    if (itemEnt->itemSoundAlias != 0) {
        event = EV_ITEM_PICKUP_QUIET;
        G_PlaySoundAlias(player,
                         itemEnt->itemSoundAlias);
    }

    if (player->client->predictItems == 0) {
        G_AddEvent(player, event,
                   itemEnt->itemIndex);
    } else {
        G_AddPredictableEvent(player, event,
                              itemEnt->itemIndex);
    }

    game_compat_touch_item_schedule_respawn(itemEnt, respawnSeconds);
}

/* VERIFIED_DECOMPILER(0x32531, 42531_BG_GetMaxPickupableAmmo.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - ammo/clip index setup, shared-cap table de-duplication, owned-weapon filtering, and clip-only branches checked against current decompiler output. */
int BG_GetMaxPickupableAmmo(gclient_t *client, int weapon)
{
    int ammoIndex = BG_AmmoForWeapon(weapon);
    int clipIndex = BG_ClipForWeapon(weapon);
    int ammoTypeSeen[128];
    int clipSeen[128];
    const weaponInfo_t *weaponInfo;
    int sharedAmmoCap;
    int remaining;

    memset(ammoTypeSeen, 0, sizeof(ammoTypeSeen));
    memset(clipSeen, 0, sizeof(clipSeen));

    weaponInfo = (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);
    sharedAmmoCap = weaponInfo->sharedAmmoCapIndex;
    if (sharedAmmoCap < 0) {
        if (BG_WeaponIsClipOnly(weapon) == 0) {
            return BG_GetAmmoTypeMax(ammoIndex) - client->ps.ammo[ammoIndex];
        }

        return BG_GetAmmoClipSize(clipIndex) - client->ps.clips[clipIndex];
    }

    remaining = BG_GetSharedAmmoCapSize(sharedAmmoCap);

    for (int candidate = 1; candidate <= BG_GetNumWeapons(); candidate++) {
        const weaponInfo_t *candidateInfo;
        int candidateSharedCap;

        if (Com_BitCheck(client->ps.weaponBits, candidate) == 0) {
            continue;
        }

        candidateInfo = BG_GetInfoForWeapon(candidate);
        candidateSharedCap = candidateInfo->sharedAmmoCapIndex;
        if (candidateSharedCap != sharedAmmoCap) {
            continue;
        }

        if (BG_WeaponIsClipOnly(candidate) == 0) {
            int candidateAmmo = BG_AmmoForWeapon(candidate);

            if (ammoTypeSeen[candidateAmmo] == 0) {
                ammoTypeSeen[candidateAmmo] = 1;
                remaining -= client->ps.ammo[candidateAmmo];
            }
        } else {
            int candidateClip = BG_ClipForWeapon(candidate);

            if (clipSeen[candidateClip] == 0) {
                clipSeen[candidateClip] = 1;
                remaining -= client->ps.clips[candidateClip];
            }
        }
    }

    return remaining;
}

/* VERIFIED_DECOMPILER(0x3272f, 4272f_BG_GetTotalAmmoReserve.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - ammo/clip index setup, non-shared return values, shared-cap owned-weapon scan, and seen-table accumulation checked against current decompiler output. */
int BG_GetTotalAmmoReserve(gclient_t *client, int weapon)
{
    int ammoIndex = BG_AmmoForWeapon(weapon);
    int clipIndex = BG_ClipForWeapon(weapon);
    int ammoTypeSeen[128];
    int clipSeen[128];
    const weaponInfo_t *weaponInfo;
    int sharedAmmoCap;
    int total = 0;

    memset(ammoTypeSeen, 0, sizeof(ammoTypeSeen));
    memset(clipSeen, 0, sizeof(clipSeen));

    weaponInfo = (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);
    sharedAmmoCap = weaponInfo->sharedAmmoCapIndex;
    if (sharedAmmoCap < 0) {
        if (BG_WeaponIsClipOnly(weapon) == 0) {
            return client->ps.ammo[ammoIndex];
        }

        return client->ps.clips[clipIndex];
    }

    for (int candidate = 1; candidate <= BG_GetNumWeapons(); candidate++) {
        const weaponInfo_t *candidateInfo;
        int candidateSharedCap;

        if (Com_BitCheck(client->ps.weaponBits, candidate) == 0) {
            continue;
        }

        candidateInfo = BG_GetInfoForWeapon(candidate);
        candidateSharedCap = candidateInfo->sharedAmmoCapIndex;
        if (candidateSharedCap != sharedAmmoCap) {
            continue;
        }

        if (BG_WeaponIsClipOnly(candidate) == 0) {
            int candidateAmmo = BG_AmmoForWeapon(candidate);

            if (ammoTypeSeen[candidateAmmo] == 0) {
                ammoTypeSeen[candidateAmmo] = 1;
                total += client->ps.ammo[candidateAmmo];
            }
        } else {
            int candidateClip = BG_ClipForWeapon(candidate);

            if (clipSeen[candidateClip] == 0) {
                clipSeen[candidateClip] = 1;
                total += client->ps.clips[candidateClip];
            }
        }
    }

    return total;
}

/* VERIFIED_DECOMPILER(0x1fc7b, 2fc7b_BG_CanItemBeGrabbed.c, VERIFY-WAVE3-ITEM-TOUCH-VISIBILITY-2026-06-17): DATAFLOW_VERIFIED - item-index guard, owner exclusion, weapon/manual and ammo capacity gates, health gate, IT_BAD error, and default deny checked against current decompiler output. */
int BG_CanItemBeGrabbed(gentity_t *itemEnt, gclient_t *client, int traceMode)
{
    int itemIndex = itemEnt->itemIndex;
    const gitem_t *itemInfo;
    itemType_t itemType;
    int weapon;

    if (itemIndex < ITEM_INDEX_MIN || itemIndex >= bg_numItems) {
        Com_Error(ERROR_DROP,
                  COM_ERROR_MARKER
                  "BG_CanItemBeGrabbed: index out of range");
    }

    itemInfo = game_compat_item_def_for_index(itemIndex);
    if (itemEnt->clientNum == client->ps.psClientNum) {
        return 0;
    }

    itemType = itemInfo->type;
    weapon = itemInfo->weapon;

    switch (itemType) {
    case IT_WEAPON:
        if (Com_BitCheck(client->ps.weaponBits, weapon) == 0) {
            if (traceMode != ITEM_TOUCH_MANUAL) {
                return 0;
            }
        } else if (BG_GetMaxPickupableAmmo(client, weapon) < 1) {
            return 0;
        }
        return 1;

    case IT_BAD:
        Com_Error(ERROR_DROP,
                  COM_ERROR_MARKER "BG_CanItemBeGrabbed: IT_BAD");
        return 0;

    case IT_AMMO:
        if (Com_BitCheck(client->ps.weaponBits, weapon) == 0) {
            if (BG_WeaponIsClipOnly(weapon) == 0 ||
                BG_GetMaxPickupableAmmo(client, weapon) < 1) {
                return 0;
            }
        } else if (BG_GetMaxPickupableAmmo(client, weapon) < 1) {
            return 0;
        }
        return 1;

    case IT_HEALTH:
        return client->ps.maxHealth > client->ps.health;

    default:
        return 0;
    }
}

/* VERIFIED_DECOMPILER(0x320c5, 420c5_BG_SetPlayerWeaponForSlot.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - ownership bit check, slot-class validation, droppable-slot bounds, and weaponSlots byte store checked against current decompiler output. */
int BG_SetPlayerWeaponForSlot(playerState_t *client, int slot, int weapon)
{
    int weaponSlot;

    if (Com_BitCheck(client->weaponBits, weapon) == 0) {
        return 0;
    }

    weaponSlot = ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->slot;
    if (weaponSlot == WEAPSLOT_NONE) {
        return 0;
    }

    if (weaponSlot < WEAPSLOT_PRIMARY_LIMIT) {
        if (slot != WEAPSLOT_PRIMARY_FIRST &&
            slot != WEAPSLOT_PRIMARY_SECOND) {
            return 0;
        }
    } else {
        if (weaponSlot > WEAPSLOT_LAST_DROPPABLE || slot != weaponSlot) {
            return 0;
        }
    }

    client->weaponSlots[slot] = (uint8_t)weapon;
    return 1;
}

/* VERIFIED_DECOMPILER(0x3217b, 4217b_BG_IsPlayerWeaponInSlot.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - ownership check, primary/fixed slot lookup, alt-chain traversal, loop termination, and return slots checked against current decompiler output. */
int BG_IsPlayerWeaponInSlot(playerState_t *client, int weapon, int includeAltWeapons)
{
    if (Com_BitCheck(client->weaponBits, weapon) == 0) {
        return 0;
    }

    int current = weapon;
    do {
        const weaponInfo_t *weaponInfo =
            (const weaponInfo_t *)BG_GetInfoForWeapon(current);
        int weaponSlot = weaponInfo->slot;

        if (weaponSlot == WEAPSLOT_NONE) {
            return 0;
        }

        if (weaponSlot < WEAPSLOT_PRIMARY_LIMIT) {
            if ((int)(int8_t)client->weaponSlots[WEAPSLOT_PRIMARY_FIRST] ==
                current) {
                return WEAPSLOT_PRIMARY_FIRST;
            }
            if ((int)(int8_t)client->weaponSlots[WEAPSLOT_PRIMARY_SECOND] ==
                current) {
                return WEAPSLOT_PRIMARY_SECOND;
            }
        } else {
            if (weaponSlot > WEAPSLOT_LAST_DROPPABLE) {
                return 0;
            }
            if ((int)(int8_t)client->weaponSlots[weaponSlot] == current) {
                return weaponSlot;
            }
        }

        if (includeAltWeapons == 0 || weaponInfo->altWeapon == 0) {
            break;
        }

        current = weaponInfo->altWeapon;
    } while (current != weapon);

    return 0;
}

/* VERIFIED_DECOMPILER(0x32286, 42286_BG_GetEmptySlotForWeapon.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - slot field read, primary empty-slot order, fixed-slot bounds, and zero return checked against current decompiler output. */
int BG_GetEmptySlotForWeapon(gclient_t *client, int weapon)
{
    int weaponSlot = ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->slot;

    if (weaponSlot == WEAPSLOT_NONE) {
        return 0;
    }

    if (weaponSlot < WEAPSLOT_PRIMARY_LIMIT) {
        if (client->ps.weaponSlots[WEAPSLOT_PRIMARY_FIRST] == 0) {
            return WEAPSLOT_PRIMARY_FIRST;
        }
        if (client->ps.weaponSlots[WEAPSLOT_PRIMARY_SECOND] == 0) {
            return WEAPSLOT_PRIMARY_SECOND;
        }
    } else if (weaponSlot <= WEAPSLOT_LAST_DROPPABLE &&
               client->ps.weaponSlots[weaponSlot] == 0) {
        return weaponSlot;
    }

    return 0;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from BG_GetStackSlotForWeapon generated body. */
static int game_compat_bg_slot_can_stack_weapon(gclient_t *client, int slot)
{
    int slottedWeapon = (int)(int8_t)client->ps.weaponSlots[slot];

    if (slottedWeapon == 0) {
        return 1;
    }

    return ((const weaponInfo_t *)BG_GetInfoForWeapon(slottedWeapon))->stackable != 0;
}

/* VERIFIED_DECOMPILER(0x32319, 42319_BG_GetStackSlotForWeapon.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - stackable gate, preferred-slot order, extracted slot-stack helper side effects, fixed-slot bounds, and zero return checked against current decompiler output. */
int BG_GetStackSlotForWeapon(gclient_t *client, int weapon, int preferredSlot)
{
    const weaponInfo_t *weaponInfo =
        (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);
    int weaponSlot;

    if (weaponInfo->stackable == 0) {
        return 0;
    }

    weaponSlot = weaponInfo->slot;
    if (weaponSlot == WEAPSLOT_NONE) {
        return 0;
    }

    if (weaponSlot < WEAPSLOT_PRIMARY_LIMIT) {
        if ((preferredSlot == WEAPSLOT_PRIMARY_FIRST ||
             preferredSlot == WEAPSLOT_PRIMARY_SECOND) &&
            game_compat_bg_slot_can_stack_weapon(client, preferredSlot) != 0) {
            return preferredSlot;
        }

        if (game_compat_bg_slot_can_stack_weapon(client, WEAPSLOT_PRIMARY_FIRST) != 0) {
            return WEAPSLOT_PRIMARY_FIRST;
        }
        if (game_compat_bg_slot_can_stack_weapon(client, WEAPSLOT_PRIMARY_SECOND) != 0) {
            return WEAPSLOT_PRIMARY_SECOND;
        }
    } else if (weaponSlot <= WEAPSLOT_LAST_DROPPABLE &&
               game_compat_bg_slot_can_stack_weapon(client, weaponSlot) != 0) {
        return weaponSlot;
    }

    return 0;
}

/* VERIFIED_DECOMPILER(0x324bb, 424bb_BG_IsPlayerWeaponAnAlt.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - initial alt lookup, self-cycle guard, alt match, null termination, and return values checked against current decompiler output. */
int BG_IsPlayerWeaponAnAlt(int weapon, int altWeapon)
{
    int current = ((const weaponInfo_t *)BG_GetInfoForWeapon(weapon))->altWeapon;

    while (current != 0) {
        if (current == altWeapon) {
            return 1;
        }
        if (current == weapon) {
            return 0;
        }

        current = ((const weaponInfo_t *)BG_GetInfoForWeapon(current))->altWeapon;
    }

    return 0;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from BG_ParseWeaponEnumField generated body. */
static qboolean game_compat_bg_parse_weapon_enum_value(const char *value,
                                        const char *const *names,
                                        int count,
                                        int *out)
{
    for (int index = 0; index < count; index++) {
        if (strcasecmp(value, names[index]) == 0) {
            *out = index;
            return qtrue;
        }
    }

    return qfalse;
}

/* VERIFIED_DECOMPILER(0x30263, 40263_FUN_00040263.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - custom enum field switch, lookup tables/counts, stores to weaponInfo offsets 0x7c/0x80/0x90/0x284/0x84/0x8c/0x390, Com_Error arguments, default bad-field return, and post-error fallthrough return checked against current decompiler output; game_compat_bg_parse_weapon_enum_value is a NOT_FROM_ORIGINAL_SOURCE extraction of the repeated strcasecmp loops. */
int BG_ParseWeaponEnumField(void *weaponInfo, const char *value, int fieldType)
{
    weaponInfo_t *wi = (weaponInfo_t *)weaponInfo;
    int enumValue;

    switch (fieldType) {
    case WEAPON_PARSE_TYPE_WEAPON_TYPE:
        if (game_compat_bg_parse_weapon_enum_value(value, bg_weaponTypeNames,
                                    WEAPTYPE_COUNT, &enumValue) != 0) {
            wi->weaponType = enumValue;
            return qtrue;
        }
        Com_Error(ERROR_DROP, COM_ERROR_MARKER
                                 "Unknown weapon type \"%s\" in \"%s\"\n",
                  value, wi->pickupName);
        break;
    case WEAPON_PARSE_TYPE_WEAPON_CLASS:
        if (game_compat_bg_parse_weapon_enum_value(value, bg_weaponClassNames,
                                    WEAPCLASS_COUNT, &enumValue) != 0) {
            wi->weaponClass = enumValue;
            return qtrue;
        }
        Com_Error(ERROR_DROP, COM_ERROR_MARKER
                                 "Unknown weapon class \"%s\" in \"%s\"\n",
                  value, wi->pickupName);
        break;
    case WEAPON_PARSE_TYPE_AMMO_TYPE:
        if (game_compat_bg_parse_weapon_enum_value(value, bg_weaponAmmoTypeNames,
                                    WEAPON_AMMO_TYPE_COUNT, &enumValue) != 0) {
            wi->ammoType = enumValue;
            return qtrue;
        }
        Com_Error(ERROR_DROP, COM_ERROR_MARKER
                                 "Unknown ammo type \"%s\" in \"%s\"\n",
                  value, wi->pickupName);
        break;
    case WEAPON_PARSE_TYPE_OVERLAY_RETICLE:
        if (game_compat_bg_parse_weapon_enum_value(value, bg_weaponOverlayReticleNames,
                                    WEAPON_OVERLAY_RETICLE_COUNT, &enumValue) != 0) {
            wi->overlayReticleAdsReduceGate = enumValue;
            return qtrue;
        }
        Com_Error(ERROR_DROP,
                  COM_ERROR_MARKER
                  "Unknown weapon overlay reticle \"%s\" in \"%s\"\n",
                  value, wi->pickupName);
        break;
    case WEAPON_PARSE_TYPE_WEAPON_SLOT:
        if (game_compat_bg_parse_weapon_enum_value(value, bg_weaponSlotNames,
                                    WEAPSLOT_COUNT, &enumValue) != 0) {
            wi->slot = enumValue;
            return qtrue;
        }
        Com_Error(ERROR_DROP, COM_ERROR_MARKER
                                 "Unknown weapon slot \"%s\" in \"%s\"\n",
                  value, wi->pickupName);
        break;
    case WEAPON_PARSE_TYPE_STANCE:
        if (game_compat_bg_parse_weapon_enum_value(value, bg_weaponStanceNames,
                                    WEAPON_STANCE_COUNT, &enumValue) != 0) {
            wi->stance = enumValue;
            return qtrue;
        }
        Com_Error(ERROR_DROP, COM_ERROR_MARKER
                                 "Unknown weapon stance \"%s\" in \"%s\"\n",
                  value, wi->pickupName);
        break;
    case WEAPON_PARSE_TYPE_GRENADE_TYPE:
        if (game_compat_bg_parse_weapon_enum_value(value, bg_weaponGrenadeTypeNames,
                                    WEAPON_GRENADE_TYPE_COUNT, &enumValue) != 0) {
            wi->projectileExplosionType = enumValue;
            return qtrue;
        }
        Com_Error(ERROR_DROP,
                  COM_ERROR_MARKER
                  "Unknown projectile explosion \"%s\" in \"%s\"\n",
                  value, wi->pickupName);
        break;
    default:
        Com_Error(ERROR_DROP, COM_ERROR_MARKER
                                 "Bad field type %i in %s\n",
                  fieldType, wi->pickupName);
        return qfalse;
    }

    return qtrue;
}

/* VERIFIED_DECOMPILER(0x30610, 40610_FUN_00040610.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - empty-string global alias, low-hunk aligned allocation, strlen+1 size, strcpy, and destination pointer store checked against current decompiler output. */
void BG_CopyWeaponString(const char **dest, const char *src)
{
    char *copy;
    size_t length;

    if (*src == '\0') {
        *dest = bg_weaponEmptyString;
        return;
    }

    length = strlen(src);
    copy = (char *)trap_Hunk_AllocLowAlignInternal(length + 1, 1);
    strcpy(copy, src);
    *dest = copy;
}

/* VERIFIED_DECOMPILER(0x30676, 40676_FUN_00040676.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - parser callback thunk delegates arguments unchanged to BG_CopyWeaponString and returns void as checked against current decompiler output. */
void BG_CopyWeaponStringForParser(char *dest, const char *src)
{
    BG_CopyWeaponString((const char **)(void *)dest, src);
}

/* VERIFIED_DECOMPILER(0x30690, 40690_FUN_00040690.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - low-hunk weaponInfo allocation size, bg_weaponInfos slot store, weaponIndex store, pickupName default, parse-field stride/type check, string-offset defaults, and return pointer checked against current decompiler output. */
weaponInfo_t *BG_AllocWeaponInfoDefaults(int weapon,
                                         const parseField_t *fields,
                                         int fieldCount)
{
    weaponInfo_t *weaponInfo =
        (weaponInfo_t *)trap_Hunk_AllocLowInternal((int)sizeof(*weaponInfo));

    bg_weaponInfos[weapon] = weaponInfo;
    weaponInfo->weaponIndex = weapon;
    BG_CopyWeaponString(&weaponInfo->pickupName, "");

    for (int fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
        if (fields[fieldIndex].type == WEAPON_PARSE_FIELD_STRING) {
            BG_CopyWeaponString((const char **)(void *)
                                    &((uint8_t *)weaponInfo)
                                        [fields[fieldIndex].offset],
                                "");
        }
    }

    return weaponInfo;
}

/* VERIFIED_DECOMPILER(0x3073b, 4073b_FUN_0004073b.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - empty-string hunk setup, default weapon-info allocation, slot-0 pickupName "none", bg_numWeapons increment/decrement behavior, path construction, debug print gate, file open/read/close order, WEAPONFILE magic validation, 8192-byte payload guard, Info_Validate, pickupName copy, ParseConfigStringToStruct arguments/callback order, and failed-parse pointer clear checked against current decompiler output. */
void BG_LoadWeaponFiles(const char **weaponFiles, int weaponFileCount)
{
    const size_t magicLength = strlen(WEAPON_FILE_MAGIC);
    int handle;
    int fileLength;
    char info[WEAPON_FILE_BUFFER_SIZE];
    char path[WEAPON_FILE_PATH_SIZE];

    bg_weaponEmptyString = (const char *)trap_Hunk_AllocLowAlignInternal(1, 1);
    ((char *)bg_weaponEmptyString)[0] = '\0';

    BG_AllocWeaponInfoDefaults(0, bg_weaponParseFields,
                               bg_iNumWeaponInfoFields);
    BG_CopyWeaponString(&bg_weaponInfos[0]->pickupName, WEAPON_AMMO_NONE_NAME);

    bg_numWeapons = 0;

    for (int weaponFile = 0; weaponFile < weaponFileCount; weaponFile++) {
        weaponInfo_t *weaponInfo;

        bg_numWeapons++;
        weaponInfo = BG_AllocWeaponInfoDefaults(bg_numWeapons,
                                                bg_weaponParseFields,
                                                bg_iNumWeaponInfoFields);

        /* ORIGINAL_BINARY_BUG(game-weapon-file-loader-stack-corruption):
         * stock builds this path with unbounded sprintf/strcat in a 64-byte
         * stack array even though FS_GetFileList can return longer names. */
        sprintf(path, "%s/", bg_szWeaponsFolder);
        strcat(path, weaponFiles[weaponFile]);

        if (bg_debugWeaponMessages.integer != 0 &&
            bg_debugWeaponMessages.integer != 2) {
            Com_DPrintf("Parsing weapon file \"%s\"...\n", path);
        }

        fileLength = trap_FS_FOpenFile(path, &handle, 0);
        if (fileLength < 1) {
            Com_Error(ERROR_DROP,
                      COM_ERROR_MARKER "Could not load weapon file '%s'",
                      path);
        }

        /* ORIGINAL_BINARY_BUG(game-weapon-file-loader-stack-corruption):
         * stock requests the complete magic without proving fileLength is at
         * least magicLength and ignores the short-read count. If untouched
         * stack bytes complete the comparison, the later subtraction is
         * negative and is forwarded to FS_Read as a huge unsigned length. */
        trap_FS_Read(info, (int)magicLength, handle);
        info[magicLength] = '\0';
        if (strncmp(info, WEAPON_FILE_MAGIC, magicLength) != 0) {
            Com_Error(ERROR_DROP,
                      COM_ERROR_MARKER
                      "\"%s\" does not appear to be a weapon file",
                      path);
        }

        if (fileLength - (int)magicLength >= WEAPON_FILE_BUFFER_SIZE) {
            Com_Error(ERROR_DROP,
                      COM_ERROR_MARKER
                      "\"%s\" Is too long of a weapon file to parse",
                      path);
        }

        memset(info, 0, sizeof(info));
        trap_FS_Read(info, fileLength - (int)magicLength, handle);
        info[fileLength - (int)magicLength] = '\0';
        trap_FS_FCloseFile(handle);

        if (Info_Validate(info) == 0) {
            Com_Error(ERROR_DROP,
                      COM_ERROR_MARKER "\"%s\" is not a valid weapon file",
                      path);
        }

        BG_CopyWeaponString(&weaponInfo->pickupName, weaponFiles[weaponFile]);

        if (ParseConfigStringToStruct(weaponInfo, bg_weaponParseFields,
                                      bg_iNumWeaponInfoFields, info,
                                      WEAPON_PARSE_CUSTOM_TYPE_MAX,
                                      BG_ParseWeaponEnumField,
                                      BG_CopyWeaponStringForParser) == 0) {
            bg_weaponInfos[bg_numWeapons] = NULL;
            bg_numWeapons--;
        }
    }
}

/* VERIFIED_DECOMPILER(0x30ab4, 40ab4_FUN_00040ab4.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - 1..bg_numWeapons loop, adsIn/adsOut <=0 default rates, reciprocal calculations, and stores to +0x4b4/+0x4b8 checked against current decompiler output. */
void BG_SetupWeaponADSRates(void)
{
    for (int weapon = 1; weapon <= bg_numWeapons; weapon++) {
        weaponInfo_t *weaponInfo = bg_weaponInfos[weapon];

        if (weaponInfo->adsInTime < 1) {
            /* Original computes 1.0f / 300.0f at runtime (0x30b2c); the
             * float result is exactly this constant. */
            weaponInfo->adsFireDelayRate = WEAPON_ADS_IN_DEFAULT_RATE;
        } else {
            /* Original i386 0x30b13 divides the raw fild'd integer in x87;
             * a (float) cast of the int would add a rounding. */
#if EMULATE_X87
            weaponInfo->adsFireDelayRate = x87f_store_f32(x87f_div(
                x87f_load_f32(1.0f), x87f_load_i32(weaponInfo->adsInTime)));
#else
            weaponInfo->adsFireDelayRate =
                1.0f / (long double)weaponInfo->adsInTime;
#endif
        }

        if (weaponInfo->adsOutTime < 1) {
            /* Original computes 1.0f / 500.0f at runtime (0x30b66). */
            weaponInfo->adsFireDelayOutRate = WEAPON_ADS_OUT_DEFAULT_RATE;
        } else {
#if EMULATE_X87
            weaponInfo->adsFireDelayOutRate = x87f_store_f32(x87f_div(
                x87f_load_f32(1.0f), x87f_load_i32(weaponInfo->adsOutTime)));
#else
            weaponInfo->adsFireDelayOutRate =
                1.0f / (long double)weaponInfo->adsOutTime;
#endif
        }
    }
}

/* VERIFIED_DECOMPILER(0x30b85, 40b85_BG_FillInWeaponItems.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - weapon item population loop, pickupModel/worldModel fallback, item field stores, ammo-item prefix scan, default weapon fallback warning, and ammo/clip index stores checked against current decompiler output; game_compat_item_def_for_index is a NOT_FROM_ORIGINAL_SOURCE typed accessor for original bg_itemlist pointer arithmetic. */
void BG_FillInWeaponItems(void)
{
    int itemIndex;

    for (itemIndex = 1; itemIndex <= bg_numWeapons; itemIndex++) {
        gitem_t *item = game_compat_item_def_for_index(itemIndex);
        const weaponInfo_t *weaponInfo = bg_weaponInfos[itemIndex];
        const char *worldOrPickupModel = weaponInfo->worldModel;

        if (weaponInfo->pickupModel != NULL &&
            weaponInfo->pickupModel[0] != '\0') {
            worldOrPickupModel = weaponInfo->pickupModel;
        }

        item->classname = weaponInfo->scriptClassname;
        item->pickupSound = weaponInfo->pickupSound;
        item->worldModel = worldOrPickupModel;
        item->iconModel = NULL;
        item->hudIcon = weaponInfo->hudIcon;
        item->ammoIcon = weaponInfo->ammoIcon;
        item->pickupName = weaponInfo->displayName;
        item->quantity = weaponInfo->startAmmo;
        item->type = IT_WEAPON;
        item->weapon = itemIndex;
        item->ammoIndex = weaponInfo->ammoIndex;
        item->clipIndex = weaponInfo->clipIndex;
    }

    for (; itemIndex < bg_numItems; itemIndex++) {
        gitem_t *item = game_compat_item_def_for_index(itemIndex);

        if (item->type == IT_AMMO) {
            for (int weapon = 1; weapon <= bg_numWeapons; weapon++) {
                const weaponInfo_t *weaponInfo = bg_weaponInfos[weapon];
                size_t weaponNameLength = strlen(weaponInfo->pickupName);

                if (Q_stricmpn(item->pickupName, weaponInfo->pickupName,
                               (int)weaponNameLength) == 0) {
                    item->weapon = weapon;
                    item->ammoIndex = weaponInfo->ammoIndex;
                    item->clipIndex = weaponInfo->clipIndex;
                    break;
                }
            }

            if (item->weapon == -1) {
                const weaponInfo_t *defaultWeaponInfo = bg_weaponInfos[1];

                Com_Printf("^3WARNING^7: Could not find weapon for ammo item %s\n",
                           item->pickupName);
                item->weapon = 1;
                item->ammoIndex = defaultWeaponInfo->ammoIndex;
                item->clipIndex = defaultWeaponInfo->clipIndex;
            }
        }
    }
}

/* VERIFIED_DECOMPILER(0x30dc4, 40dc4_BG_SetupAmmoIndexes.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - lowercase normalization, ammo-name table lookup/append, ammoIndex stores, max-ammo conflict scan, and Com_Error arguments checked against current decompiler output. */
void BG_SetupAmmoIndexes(void)
{
    for (int weapon = 1; weapon <= bg_numWeapons; weapon++) {
        weaponInfo_t *weaponInfo = bg_weaponInfos[weapon];
        int ammoIndex;

        Q_strlwr((char *)weaponInfo->ammoName);
        for (ammoIndex = 0; ammoIndex < bg_numAmmoTypes; ammoIndex++) {
            if (Q_stricmp(bg_ammoTypeNames[ammoIndex], weaponInfo->ammoName) == 0) {
                weaponInfo->ammoIndex = ammoIndex;
                if (bg_ammoTypeMax[ammoIndex] != weaponInfo->maxAmmo &&
                    ammoIndex != 0) {
                    for (int previous = 1; previous < weapon; previous++) {
                        weaponInfo_t *previousWeaponInfo = bg_weaponInfos[previous];

                        if (Q_stricmp(bg_ammoTypeNames[ammoIndex],
                                      previousWeaponInfo->ammoName) == 0 &&
                            previousWeaponInfo->maxAmmo ==
                                bg_ammoTypeMax[ammoIndex]) {
                            Com_Error(ERROR_DROP,
                                      COM_ERROR_MARKER
                                      "Max ammo mismatch for \"%s\" ammo: "
                                      "'%s\" set it to %i, but \"%s\" "
                                      "already set it to %i.\n",
                                      weaponInfo->ammoName, weaponInfo->pickupName,
                                      weaponInfo->maxAmmo,
                                      previousWeaponInfo->pickupName,
                                      previousWeaponInfo->maxAmmo);
                        }
                    }
                }
                break;
            }
        }

        if (ammoIndex == bg_numAmmoTypes) {
            bg_ammoTypeNames[ammoIndex] = weaponInfo->ammoName;
            bg_ammoTypeMax[ammoIndex] = weaponInfo->maxAmmo;
            weaponInfo->ammoIndex = ammoIndex;
            bg_numAmmoTypes++;
        }
    }
}

/* VERIFIED_DECOMPILER(0x30f9b, 40f9b_BG_SetupSharedAmmoIndexes.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - default -1 store, empty-name skip, debug print, shared-cap table lookup/append, size conflict scan, and Com_Error arguments checked against current decompiler output. */
void BG_SetupSharedAmmoIndexes(void)
{
    for (int weapon = 1; weapon <= bg_numWeapons; weapon++) {
        weaponInfo_t *weaponInfo = bg_weaponInfos[weapon];
        int sharedAmmoCapIndex;

        weaponInfo->sharedAmmoCapIndex = -1;
        if (weaponInfo->sharedAmmoCapName[0] == '\0') {
            continue;
        }

        Com_DPrintf("%s: %s\n", weaponInfo->pickupName,
                    weaponInfo->sharedAmmoCapName);
        Q_strlwr((char *)weaponInfo->sharedAmmoCapName);

        for (sharedAmmoCapIndex = 0; sharedAmmoCapIndex < bg_numSharedAmmoCaps;
             sharedAmmoCapIndex++) {
            if (Q_stricmp(bg_sharedAmmoCapNames[sharedAmmoCapIndex],
                          weaponInfo->sharedAmmoCapName) == 0) {
                weaponInfo->sharedAmmoCapIndex = sharedAmmoCapIndex;
                if (bg_sharedAmmoCapSizes[sharedAmmoCapIndex] !=
                        weaponInfo->sharedAmmoCap &&
                    sharedAmmoCapIndex != 0) {
                    for (int previous = 1; previous < weapon; previous++) {
                        weaponInfo_t *previousWeaponInfo = bg_weaponInfos[previous];

                        if (Q_stricmp(bg_sharedAmmoCapNames[sharedAmmoCapIndex],
                                      previousWeaponInfo->sharedAmmoCapName) == 0 &&
                            previousWeaponInfo->sharedAmmoCap ==
                                bg_sharedAmmoCapSizes[sharedAmmoCapIndex]) {
                            Com_Error(ERROR_DROP,
                                      COM_ERROR_MARKER
                                      "Shared ammo cap mismatch for \"%s\" "
                                      "shared ammo cap: '%s\" set it to %i, "
                                      "but \"%s\" already set it to %i.\n",
                                      weaponInfo->sharedAmmoCapName,
                                      weaponInfo->pickupName,
                                      weaponInfo->sharedAmmoCap,
                                      previousWeaponInfo->pickupName,
                                      previousWeaponInfo->sharedAmmoCap);
                        }
                    }
                }
                break;
            }
        }

        if (sharedAmmoCapIndex == bg_numSharedAmmoCaps) {
            bg_sharedAmmoCapNames[sharedAmmoCapIndex] =
                weaponInfo->sharedAmmoCapName;
            bg_sharedAmmoCapSizes[sharedAmmoCapIndex] =
                weaponInfo->sharedAmmoCap;
            weaponInfo->sharedAmmoCapIndex = sharedAmmoCapIndex;
            bg_numSharedAmmoCaps++;
        }
    }
}

/* VERIFIED_DECOMPILER(0x311b7, 411b7_BG_SetupClipIndexes.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - lowercase normalization, clip-name table lookup/append, clipIndex stores, clip-size conflict scan, and original ammo-name error argument checked against current decompiler output. */
void BG_SetupClipIndexes(void)
{
    for (int weapon = 1; weapon <= bg_numWeapons; weapon++) {
        weaponInfo_t *weaponInfo = bg_weaponInfos[weapon];
        int clipIndex;

        Q_strlwr((char *)weaponInfo->clipName);
        for (clipIndex = 0; clipIndex < bg_numAmmoClips; clipIndex++) {
            if (Q_stricmp(bg_ammoClipNames[clipIndex], weaponInfo->clipName) == 0) {
                weaponInfo->clipIndex = clipIndex;
                if (bg_ammoClipSizes[clipIndex] != weaponInfo->clipSize &&
                    clipIndex != 0) {
                    for (int previous = 1; previous < weapon; previous++) {
                        weaponInfo_t *previousWeaponInfo = bg_weaponInfos[previous];

                        if (Q_stricmp(bg_ammoClipNames[clipIndex],
                                      previousWeaponInfo->clipName) == 0 &&
                            previousWeaponInfo->clipSize ==
                                bg_ammoClipSizes[clipIndex]) {
                            /* ORIGINAL_BINARY_BUG(cgame-mp-clip-size-error-uses-ammo-name):
                             * stock passes ammoName to the diagnostic that
                             * labels this value as the clip name. */
                            Com_Error(ERROR_DROP,
                                      COM_ERROR_MARKER
                                      "Clip Size mismatch for \"%s\" clip: "
                                      "'%s\" set it to %i, but \"%s\" "
                                      "already set it to %i.\n",
                                      weaponInfo->ammoName, weaponInfo->pickupName,
                                      weaponInfo->clipSize,
                                      previousWeaponInfo->pickupName,
                                      previousWeaponInfo->clipSize);
                        }
                    }
                }
                break;
            }
        }

        if (clipIndex == bg_numAmmoClips) {
            bg_ammoClipNames[clipIndex] = weaponInfo->clipName;
            bg_ammoClipSizes[clipIndex] = weaponInfo->clipSize;
            weaponInfo->clipIndex = clipIndex;
            bg_numAmmoClips++;
        }
    }
}

/* VERIFIED_DECOMPILER(0x3138e, 4138e_FUN_0004138e.c, VERIFY-PACKET-AMMO-SLOTS-2026-06-17): DATAFLOW_VERIFIED - altWeapon reset, name-chain resolution, slot/stackable compatibility errors, missing-alt error, cycle validation, and stores checked against current decompiler output. */
void BG_SetupAltWeaponIndexes(void)
{
    for (int weapon = 1; weapon <= bg_numWeapons; weapon++) {
        bg_weaponInfos[weapon]->altWeapon = 0;
    }

    for (int weapon = 1; weapon <= bg_numWeapons; weapon++) {
        weaponInfo_t *weaponInfo = bg_weaponInfos[weapon];
        weaponInfo_t *current;

        if (weaponInfo->altWeapon != 0 || weaponInfo->altWeaponName[0] == '\0') {
            continue;
        }

        current = weaponInfo;
        while (current->altWeapon == 0) {
            int altWeapon;

            for (altWeapon = 1; altWeapon <= bg_numWeapons; altWeapon++) {
                weaponInfo_t *altWeaponInfo = bg_weaponInfos[altWeapon];

                if (strcasecmp(current->altWeaponName,
                               altWeaponInfo->pickupName) == 0) {
                    current->altWeapon = altWeapon;
                    if (current->slot != altWeaponInfo->slot) {
                        Com_Error(ERROR_DROP,
                                  COM_ERROR_MARKER
                                  "weapon '%s' does not have same weaponSlot "
                                  "setting as its alt weapon '%s'",
                                  current->pickupName, altWeaponInfo->pickupName);
                    }
                    if (current->stackable != altWeaponInfo->stackable) {
                        Com_Error(ERROR_DROP,
                                  COM_ERROR_MARKER
                                  "weapon '%s' does not have same slotStackable "
                                  "setting as its alt weapon '%s'",
                                  current->pickupName, altWeaponInfo->pickupName);
                    }
                    break;
                }
            }

            if (current->altWeapon == 0) {
                Com_Error(ERROR_DROP,
                          COM_ERROR_MARKER
                          "could not find altWeapon '%s' for weapon '%s'",
                          current->altWeaponName, current->pickupName);
            }

            current = bg_weaponInfos[altWeapon];
        }

        if (current != weaponInfo) {
            Com_Error(ERROR_DROP,
                      COM_ERROR_MARKER
                      "weapon '%s' has a bad altWeapon '%s'",
                      weaponInfo->pickupName, weaponInfo->altWeaponName);
        }
    }
}

/* VERIFIED_DECOMPILER(0x315c3, 415c3_FUN_000415c3.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - 1..bg_numWeapons loop, nonempty hintString gate, G_GetHintStringIndex arguments, failure branch, and max hint string constant checked against current decompiler output. */
void BG_SetupWeaponHintStrings(void)
{
    for (int weapon = 1; weapon <= bg_numWeapons; weapon++) {
        weaponInfo_t *weaponInfo = bg_weaponInfos[weapon];

        if (weaponInfo->hintString[0] != '\0' &&
            G_GetHintStringIndex(&weaponInfo->hintStringIndex,
                                 weaponInfo->hintString) == 0) {
            Com_Error(ERROR_DROP,
                      COM_ERROR_MARKER
                      "Too many different hintstring values on weapons. "
                      "Max allowed is %i different strings",
                      HINTSTRING_WEAPON_MAX);
        }
    }
}

/* VERIFIED_DECOMPILER(0x31659, 41659_compare_weaponfile_names.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - qsort comparator loads both const char * entries, calls Q_stricmp(left, right), and returns the preserved EAX result; disassembly confirms return behavior where the decompiler inferred void. */
int compare_weaponfile_names(const void *a, const void *b)
{
    const char *const *left = (const char *const *)a;
    const char *const *right = (const char *const *)b;

    return Q_stricmp(*left, *right);
}

/* VERIFIED_DECOMPILER(0x31693, 41693_BG_SetupWeaponInfo.c, VERIFY-PACKET-WEAPON-INFO-SETUP-2026-06-17): DATAFLOW_VERIFIED - debug banners, weapon-info memory acquisition, ammo/clip table initialization, weapon-file list loading/cap checks, filename pointer collection/sort/configstring setup, load-vs-already-loaded paths, setup call order, and final banner checked against current decompiler output; game_compat_bg_init_stock_item_list is NOT_FROM_ORIGINAL_SOURCE seeding for recovered bg_itemlist static data and has no standalone original call. */
void BG_SetupWeaponInfo(void)
{
    char weaponFileConfigstring[WEAPON_FILE_CONFIGSTRING_BUFFER_SIZE];
    char weaponFileList[WEAPON_FILE_LIST_BUFFER_SIZE];
    const char *weaponFiles[WEAPON_FILE_MAX];
    int alreadyLoaded;

    Com_DPrintf("----------------------\n");
    Com_DPrintf("Game: BG_SetupWeaponInfo\n");

    game_compat_bg_init_stock_item_list();

    bg_weaponInfos =
        trap_GetWeaponInfoMemory(
            WEAPON_INFO_MAX * (int)sizeof(*bg_weaponInfos),
            &alreadyLoaded);
    if (bg_weaponInfos == NULL) {
        Com_Error(ERROR_DROP,
                  COM_ERROR_MARKER "Could not allocate WeaponInfo array\n");
    }

    memset(bg_ammoTypeNames, 0, sizeof(bg_ammoTypeNames));
    bg_ammoTypeMax[0] = 0;
    /* VERIFIED_ORIGINAL(0x31693 BG_SetupWeaponInfo): the i386 module seeds
     * ammo/clip type slot 0 with the rodata string "none"; the empty string is
     * only the weapon-file list extension and becomes slot 1 when parsed. */
    bg_ammoTypeNames[0] = WEAPON_AMMO_NONE_NAME;
    bg_numAmmoTypes = 1;

    memset(bg_ammoClipNames, 0, sizeof(bg_ammoClipNames));
    bg_ammoClipSizes[0] = 0;
    bg_ammoClipNames[0] = WEAPON_AMMO_NONE_NAME;
    bg_numAmmoClips = 1;

    memset(weaponFiles, 0, sizeof(weaponFiles));

    if (alreadyLoaded == 0) {
        int weaponFileCount =
            trap_FS_GetFileList(bg_szWeaponsFolder,
                                WEAPON_FILE_LIST_EXTENSION,
                                weaponFileList, sizeof(weaponFileList));
        char *weaponFileName = weaponFileList;

        if (weaponFileCount < 1) {
            Com_Error(ERROR_DROP,
                      COM_ERROR_MARKER "No weapon files found in %s.\n",
                      bg_szWeaponsFolder);
        }
        if (weaponFileCount > WEAPON_FILE_MAX) {
            Com_Error(ERROR_DROP,
                      COM_ERROR_MARKER
                      "Max number of weapons allowed is %i, found %i.\n",
                      WEAPON_FILE_MAX, weaponFileCount);
        }

        for (int weaponFile = 0;
             weaponFile < weaponFileCount && weaponFile < WEAPON_FILE_MAX;
             weaponFile++) {
            size_t weaponFileNameLength = strlen(weaponFileName);

            if (bg_debugWeaponMessages.integer != 0 &&
                bg_debugWeaponMessages.integer != 2) {
                Com_DPrintf("Getting weapon file \"%s/%s\" for parsing\n",
                            bg_szWeaponsFolder, weaponFileName);
            }

            weaponFiles[weaponFile] = weaponFileName;
            weaponFileName = &weaponFileName[weaponFileNameLength + 1];
        }

        qsort(weaponFiles, (size_t)weaponFileCount, sizeof(weaponFiles[0]),
              compare_weaponfile_names);

        weaponFileConfigstring[0] = '\0';
        for (int weaponFile = 0; weaponFile < weaponFileCount; weaponFile++) {
            if (weaponFile > 0) {
                strcat(weaponFileConfigstring, " ");
            }
            strcat(weaponFileConfigstring, weaponFiles[weaponFile]);
        }

        trap_SetConfigstring(WEAPON_CONFIGSTRING_FILES,
                             weaponFileConfigstring);
        BG_LoadWeaponFiles(weaponFiles, weaponFileCount);
    } else {
        bg_numWeapons = 0;
        for (int weapon = 1;
             weapon < WEAPON_INFO_MAX && bg_weaponInfos[weapon] != NULL;
             weapon++) {
            bg_numWeapons++;
        }
    }

    BG_SetupWeaponADSRates();
    BG_SetupAmmoIndexes();
    BG_SetupSharedAmmoIndexes();
    BG_SetupClipIndexes();
    BG_FillInWeaponItems();
    BG_SetupAltWeaponIndexes();
    BG_SetupWeaponHintStrings();

    Com_DPrintf("----------------------\n");
}

/* VERIFIED_DECOMPILER(0x31a19, 41a19_BG_GetInfoForWeapon.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - bg_weaponInfos indexed load and pointer return checked against current decompiler output. */
const weaponInfo_t *BG_GetInfoForWeapon(int weapon)
{
    return bg_weaponInfos[weapon];
}

/* VERIFIED_DECOMPILER(0x31a3e, 41a3e_BG_GetWeaponForInfo.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - weaponInfo +0x000 weaponIndex load and return checked against current decompiler output. */
int BG_GetWeaponForInfo(const weaponInfo_t *weaponInfo)
{
    return weaponInfo->weaponIndex;
}

/* VERIFIED_DECOMPILER(0x300a5, 400a5_FUN_000400a5.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - add-0.5 rounding cast and integer return checked against current decompiler output. */
/* NOT_FROM_ORIGINAL_SOURCE: source-level factoring of original PM_StepDeltaRound (0x300a5); no standalone original body. */
int game_compat_bg_round_float_plus_half(float value)
{
    static const float half = 0.5f;

    /* value + 0.5f kept 80-bit, fistp-direct truncate -> shim. */
#if EMULATE_X87
    return x87f_store_i32_trunc(
        x87f_add(x87f_load_f32(value), x87f_load_f32(half)));
#elif defined(__x86_64__)
    return CODUO_X87_TRUNCATE_I32(
        (long double)value + (long double)half);
#else
    return (int)(value + half);
#endif
}

/* VERIFIED_DECOMPILER(0x300e3, 400e3_FUN_000400e3.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - float absolute value promoted to long double return checked against current decompiler output. */
long double BG_FloatAbs(float value)
{
    return (long double)fabsf(value);
}

/* VERIFIED_DECOMPILER(0x30100, 40100_BG_GetWeaponTypeName.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - bg_weaponTypeNames indexed pointer return checked against current decompiler output. */
const char *BG_GetWeaponTypeName(int weaponType)
{
    return bg_weaponTypeNames[weaponType];
}

/* VERIFIED_DECOMPILER(0x3011c, 4011c_BG_Bullet_Endpos.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - tan/spread constants, gunrandom argument order, muzzle vector offsets, and sequential end-vector stores checked against current decompiler output. */
void BG_Bullet_Endpos(float spread, float *end, const float *muzzlePoints)
{
    const float *forward = muzzlePoints;
    const float *right = &muzzlePoints[3];
    const float *up = &muzzlePoints[6];
    const float *origin = &muzzlePoints[9];
    float x;
    float y;
    float radius;

    /* Stock 0x30135: (spread*PI)/180 (QWORD double consts) kept 80-bit, rounds to
     * double at the libm tan() call boundary; (float)tan * DISTANCE is a single
     * mul (native).  Only the tan argument needs the shim. */
#if EMULATE_X87
    double tanArg = x87f_store_f64(x87f_div(
        x87f_mul(x87f_load_f32(spread), x87f_load_f64(WEAPON_BOB_PI)),
        x87f_load_f64(DEGREES_PER_HALF_CIRCLE)));
    radius = (float)tan(tanArg) * BULLET_TRACE_DISTANCE;
#else
    radius = (float)tan((double)((spread * WEAPON_BOB_PI) /
                                  DEGREES_PER_HALF_CIRCLE)) *
             BULLET_TRACE_DISTANCE;
#endif
    gunrandom(&x, &y);
    x *= radius;
    y *= radius;

    /* Stock 0x30189..: forward[k]*DIST + origin[k], then += right[k]*x and
     * += up[k]*y; each MA kept 80-bit, one store -> shim. */
#if EMULATE_X87
    for (int k = 0; k < 3; k++) {
        end[k] = x87f_store_f32(x87f_add(
            x87f_mul(x87f_load_f32(forward[k]),
                     x87f_load_f32(BULLET_TRACE_DISTANCE)),
            x87f_load_f32(origin[k])));
        end[k] = x87f_store_f32(x87f_add(
            x87f_mul(x87f_load_f32(right[k]), x87f_load_f32(x)),
            x87f_load_f32(end[k])));
        end[k] = x87f_store_f32(x87f_add(
            x87f_mul(x87f_load_f32(up[k]), x87f_load_f32(y)),
            x87f_load_f32(end[k])));
    }
#else
    end[0] = origin[0] + forward[0] * BULLET_TRACE_DISTANCE;
    end[1] = origin[1] + forward[1] * BULLET_TRACE_DISTANCE;
    end[2] = origin[2] + forward[2] * BULLET_TRACE_DISTANCE;
    end[0] += right[0] * x;
    end[1] += right[1] * x;
    end[2] += right[2] * x;
    end[0] += up[0] * y;
    end[1] += up[1] * y;
    end[2] += up[2] * y;
#endif
}

/* VERIFIED_DECOMPILER(0x31a48, 41a48_BG_GetNumWeapons.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; bg_numWeapons global return checked against current decompiler output. */
int BG_GetNumWeapons(void)
{
    return bg_numWeapons;
}

/* VERIFIED_DECOMPILER(0x31a60, 41a60_BG_GetNumAmmoTypes.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; bg_numAmmoTypes global return checked against current decompiler output. */
int BG_GetNumAmmoTypes(void)
{
    return bg_numAmmoTypes;
}

/* VERIFIED_DECOMPILER(0x31a78, 41a78_BG_GetAmmoTypeMax.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; bg_ammoTypeMax indexed return checked against current decompiler output. */
int BG_GetAmmoTypeMax(int ammoIndex)
{
    return bg_ammoTypeMax[ammoIndex];
}

/* VERIFIED_DECOMPILER(0x31a94, 41a94_BG_GetNumAmmoClips.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; bg_numAmmoClips global return checked against current decompiler output. */
int BG_GetNumAmmoClips(void)
{
    return bg_numAmmoClips;
}

/* VERIFIED_DECOMPILER(0x31aac, 41aac_BG_GetAmmoClipSize.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; bg_ammoClipSizes indexed return checked against current decompiler output. */
int BG_GetAmmoClipSize(int clipIndex)
{
    return bg_ammoClipSizes[clipIndex];
}

/* VERIFIED_DECOMPILER(0x31ac8, 41ac8_BG_GetSharedAmmoCapSize.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; bg_sharedAmmoCapSizes indexed return checked against current decompiler output. */
int BG_GetSharedAmmoCapSize(int sharedAmmoCapIndex)
{
    return bg_sharedAmmoCapSizes[sharedAmmoCapIndex];
}

/* VERIFIED_DECOMPILER(0x31ae4, 41ae4_BG_GetAmmoTypeName.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; bg_ammoTypeNames indexed return checked against current decompiler output. */
const char *BG_GetAmmoTypeName(int ammoIndex)
{
    return bg_ammoTypeNames[ammoIndex];
}

/* VERIFIED_DECOMPILER(0x31b00, 41b00_BG_GetAmmoClipName.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; bg_ammoClipNames indexed return checked against current decompiler output. */
const char *BG_GetAmmoClipName(int clipIndex)
{
    return bg_ammoClipNames[clipIndex];
}

/* VERIFIED_DECOMPILER(0x31b1c, 41b1c_BG_GetAmmoTypeForName.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; ammo-type scan bound, Q_stricmp argument order, diagnostic string, and zero fallback checked against current decompiler output. */
int BG_GetAmmoTypeForName(const char *name)
{
    int ammoIndex;

    for (ammoIndex = 0; ammoIndex < bg_numAmmoTypes; ammoIndex++) {
        if (Q_stricmp(bg_ammoTypeNames[ammoIndex], name) == 0) {
            return ammoIndex;
        }
    }

    Com_DPrintf("Couldn't find ammo type \"%s\"\n", name);
    return 0;
}

/* VERIFIED_DECOMPILER(0x31b93, 41b93_BG_GetAmmoClipForName.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; ammo-clip scan bound, Q_stricmp argument order, diagnostic string, and zero fallback checked against current decompiler output. */
int BG_GetAmmoClipForName(const char *name)
{
    int clipIndex;

    for (clipIndex = 0; clipIndex < bg_numAmmoClips; clipIndex++) {
        if (Q_stricmp(bg_ammoClipNames[clipIndex], name) == 0) {
            return clipIndex;
        }
    }

    Com_DPrintf("Couldn't find ammo clip \"%s\"\n", name);
    return 0;
}

/* VERIFIED_DECOMPILER(0x31c0a, 41c0a_BG_GetWeaponSlotForName.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; 0..7 slot scan, strcasecmp argument order, matching slot return, and zero fallback checked against current decompiler output. */
int BG_GetWeaponSlotForName(const char *name)
{
    int slot;

    for (slot = 0; slot < WEAPSLOT_COUNT; slot++) {
        if (strcasecmp(name, bg_weaponSlotNames[slot]) == 0) {
            return slot;
        }
    }

    return WEAPSLOT_NONE;
}

/* VERIFIED_DECOMPILER(0x31c67, 41c67_BG_GetWeaponSlotNameForIndex.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; bg_weaponSlotNames indexed return checked against current decompiler output. */
const char *BG_GetWeaponSlotNameForIndex(int slot)
{
    return bg_weaponSlotNames[slot];
}

/* VERIFIED_DECOMPILER(0x31c83, 41c83_BG_GetWeaponIndexForName.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - 0..bg_numWeapons scan, pickupName compare, byte-masked return, and missing-weapon diagnostic checked against current decompiler output. */
int BG_GetWeaponIndexForName(const char *name)
{
    int weapon;

    for (weapon = 0; weapon <= bg_numWeapons; weapon++) {
        const weaponInfo_t *weaponInfo = bg_weaponInfos[weapon];

        if (Q_stricmp(name, weaponInfo->pickupName) == 0) {
            return weapon & 0xff;
        }
    }

    Com_DPrintf("Couldn't find weapon \"%s\"\n", name);
    return 0;
}

/* VERIFIED_DECOMPILER(0x31d07, 41d07_BG_IsAimDownSightWeapon.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - BG_GetInfoForWeapon call and +0x328 adsEnabled return checked against current decompiler output. */
int BG_IsAimDownSightWeapon(int weapon)
{
    const weaponInfo_t *weaponInfo =
        (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);

    return weaponInfo->adsEnabled;
}

/* VERIFIED_DECOMPILER(0x31d30, 41d30_BG_GivePlayerWeapon.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - ownership/class gates, registration, bitsets, slot assignment, alt-chain loop, rechamber-bit clears, and return values checked against current decompiler output. */
int BG_GivePlayerWeapon(playerState_t *client, int weapon)
{
    const weaponInfo_t *weaponInfo;
    uint32_t slot;
    int altWeapon;

    if (Com_BitCheck(client->weaponBits, weapon) != 0) {
        return 0;
    }

    weaponInfo = (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);
    if (weaponInfo->weaponClass == WEAPCLASS_TURRET ||
        weaponInfo->weaponClass == WEAPCLASS_NON_PLAYER) {
        return 0;
    }

    RegisterItem(weapon, qtrue);
    Com_BitSet(client->weaponBits, weapon);
    Com_BitClear(client->weaponRechamberBits, weapon);

    slot = (uint32_t)weaponInfo->slot;
    if (slot != WEAPSLOT_NONE) {
        if (slot < WEAPSLOT_PRIMARY_LIMIT) {
            if (client->weaponSlots[WEAPSLOT_PRIMARY_FIRST] == 0) {
                client->weaponSlots[WEAPSLOT_PRIMARY_FIRST] = (uint8_t)weapon;
            } else if (client->weaponSlots[WEAPSLOT_PRIMARY_SECOND] == 0) {
                client->weaponSlots[WEAPSLOT_PRIMARY_SECOND] = (uint8_t)weapon;
            }
        } else if (slot <= WEAPSLOT_LAST_DROPPABLE &&
                   client->weaponSlots[slot] == 0) {
            client->weaponSlots[slot] = (uint8_t)weapon;
        }
    }

    altWeapon = weaponInfo->altWeapon;
    while (altWeapon != 0) {
        if (Com_BitCheck(client->weaponBits, altWeapon) != 0) {
            break;
        }

        RegisterItem(altWeapon, qtrue);
        Com_BitSet(client->weaponBits, altWeapon);
        /* ORIGINAL_BINARY_BUG(game-give-player-weapon-alt-rechamber-stale):
         * stock repeatedly clears the originally requested weapon's bit, not
         * the alternate weapon bit just granted by this loop iteration. */
        Com_BitClear(client->weaponRechamberBits, weapon);

        weaponInfo = (const weaponInfo_t *)BG_GetInfoForWeapon(altWeapon);
        altWeapon = weaponInfo->altWeapon;
    }

    return 1;
}

/* VERIFIED_DECOMPILER(0x31efc, 41efc_BG_TakePlayerWeapon.c, VERIFY-PACKET-WEAPON-GETTERS-2026-06-17): DATAFLOW_VERIFIED - ownership gate, slot clearing/replacement scan, weapon and alt-chain bit clears, and return values checked against current decompiler output. */
int BG_TakePlayerWeapon(playerState_t *client, int weapon)
{
    const weaponInfo_t *weaponInfo;
    int slot;
    int altWeapon;

    if (Com_BitCheck(client->weaponBits, weapon) == 0) {
        return 0;
    }

    weaponInfo = (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);
    slot = BG_IsPlayerWeaponInSlot(client, weapon, qtrue);
    if (slot != 0) {
        if (weaponInfo->stackable == 0) {
            client->weaponSlots[slot] = 0;
        } else {
            int replacement;

            for (replacement = 1; replacement <= bg_numWeapons; replacement++) {
                /* ORIGINAL_BINARY_BUG(cgame-mp-take-weapon-replacement-ignores-candidate-slot):
                 * stock reuses the removed weapon's stackable/slot definition;
                 * it never tests the replacement candidate's slot class. */
                if (weaponInfo->stackable != 0 &&
                    Com_BitCheck(client->weaponBits, replacement) != 0 &&
                    BG_IsPlayerWeaponInSlot(client, replacement, qtrue) == 0) {
                    client->weaponSlots[slot] = (uint8_t)replacement;
                    break;
                }
            }

            if (replacement > bg_numWeapons) {
                client->weaponSlots[slot] = 0;
            }
        }
    }

    Com_BitClear(client->weaponBits, weapon);

    altWeapon = weaponInfo->altWeapon;
    while (altWeapon != 0 &&
           Com_BitCheck(client->weaponBits, altWeapon) != 0) {
        Com_BitClear(client->weaponBits, altWeapon);
        weaponInfo = (const weaponInfo_t *)BG_GetInfoForWeapon(altWeapon);
        altWeapon = weaponInfo->altWeapon;
    }

    return 1;
}

int PM_WeaponAmmoAvailable(int weapon);
qboolean PM_WeaponClipEmpty(int weapon);
void PM_WeaponUseAmmo(int weapon, int amount);
void PM_Weapon_PrintWeaponState(void);
void PM_Weapon_PrintWeaponAnim(void);

/* VERIFIED_DECOMPILER(0x33c28, 43c28_BG_WeaponAmmo.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; ammo/clip index calls and client ammo-plus-clip field loads checked against current decompiler output. */
int BG_WeaponAmmo(gclient_t *client, int weapon)
{
    int ammoIndex = BG_AmmoForWeapon(weapon);
    int clipIndex = BG_ClipForWeapon(weapon);

    return client->ps.ammo[ammoIndex] + client->ps.clips[clipIndex];
}

static int PMDebugLastWeaponState;
static uint32_t PMDebugLastWeaponAnim;
static const char *PMDebugPrefix = "pm";

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from generated weapon-position code. */
static void game_compat_weapon_move_target_offsets(const gclient_t *client,
                                    const weaponInfo_t *wi,
                                    float fraction,
                                    vec3_t out)
{
    uint32_t entityStateFlags = client->ps.entityStateFlags;
    const float *offsets;

    if ((client->ps.playerStateFlags & PLAYERSTATE_FLAG_WEAPON_ROT_SPRINT) != 0) {
        offsets = wi->sprintRot;
    } else if ((entityStateFlags & PS_FLAG_WEAPON_ROT_PRONE) != 0) {
        offsets = wi->proneRot;
    } else if ((entityStateFlags & PS_FLAG_WEAPON_ROT_CROUCH) != 0) {
        offsets = wi->duckedRot;
    } else {
        offsets = wi->standRot;
    }

    out[0] = offsets[0] * fraction;
    out[1] = offsets[1] * fraction;
    out[2] = offsets[2] * fraction;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from generated weapon-position code. */
static float game_compat_weapon_move_threshold(const gclient_t *client, const weaponInfo_t *wi)
{
    uint32_t entityStateFlags = client->ps.entityStateFlags;

    if ((client->ps.playerStateFlags & PLAYERSTATE_FLAG_WEAPON_ROT_SPRINT) != 0) {
        return wi->sprintRotMinSpeed;
    }
    if ((entityStateFlags & PS_FLAG_WEAPON_ROT_PRONE) != 0) {
        return wi->proneRotMinSpeed;
    }
    if ((entityStateFlags & PS_FLAG_WEAPON_ROT_CROUCH) != 0) {
        return wi->duckedRotMinSpeed;
    }

    return wi->standRotMinSpeed;
}

/* VERIFIED_DECOMPILER(0x33c78, 43c78_FUN_00043c78.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; reload-start add gate, ammo/clip fill amount clamps, reloadAmmoAdd clamps, and ammo/clip stores checked against current decompiler output. */
void PM_FillCurrentWeaponClip(void)
{
    playerState_t *client = pm->ps;
    int weaponState = client->weaponState;
    qboolean reloadStartState =
        weaponState == WEAPON_STATE_RELOAD_START ||
        weaponState == WEAPON_STATE_RELOAD_START_INTERRUPT;
    int reloadStartAmmoAdd = 0;

    if (reloadStartState) {
        reloadStartAmmoAdd = pml.weaponInfo->reloadStartAmmoAdd;
        if (reloadStartAmmoAdd == 0) {
            return;
        }
    }

    int weapon = client->currentWeapon;
    int ammoIndex = BG_AmmoForWeapon(weapon);
    int clipIndex = BG_ClipForWeapon(weapon);
    int clipSize = BG_GetAmmoClipSize(clipIndex);
    int amount = clipSize - client->clips[clipIndex];

    if (client->ammo[ammoIndex] < amount) {
        amount = client->ammo[ammoIndex];
    }

    if (reloadStartState) {
        if (reloadStartAmmoAdd < clipSize && reloadStartAmmoAdd < amount) {
            amount = reloadStartAmmoAdd;
        }
    } else {
        int reloadAmmoAdd = pml.weaponInfo->reloadAmmoAdd;

        if (reloadAmmoAdd != 0 && reloadAmmoAdd < clipSize &&
            reloadAmmoAdd < amount) {
            amount = reloadAmmoAdd;
        }
    }

    if (amount != 0) {
        client->ammo[ammoIndex] -= amount;
        client->clips[clipIndex] += amount;
    }
}

/* VERIFIED_DECOMPILER(0x33f9c, 43f9c_FUN_00043f9c.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; pmType/button gates, gas direct animation store, and toggle-bit animation store checked against current decompiler output. */
void PM_StartWeaponAnim(uint32_t anim)
{
    playerState_t *client = pm->ps;

    if (client->pmType < PM_TYPE_ANIM_SCRIPT_LIMIT &&
        pm->command.weapon != 0) {
        if (pml.weaponInfo->weaponType ==
            WEAPTYPE_GAS) {
            client->weaponAnim = anim;
        } else {
            client->weaponAnim =
                ((client->weaponAnim & WEAPON_ANIM_TOGGLE_BIT) ^
                 WEAPON_ANIM_TOGGLE_BIT) |
                anim;
        }
    }
}

/* VERIFIED_DECOMPILER(0x34024, 44024_FUN_00044024.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; weapon-animation enabled gate, index-mask compare, and PM_StartWeaponAnim call checked against current decompiler output. */
void PM_ContinueWeaponAnim(uint32_t anim)
{
    if (pm->command.weapon != 0 &&
        (pm->ps->weaponAnim & WEAPON_ANIM_INDEX_MASK) != anim) {
        PM_StartWeaponAnim(anim);
    }
}

/* VERIFIED_DECOMPILER(0x34073, 44073_FUN_00044073.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; idle animation continuation and weapon-state clear checked against current decompiler output. */
void PM_Weapon_FinishRechamber(void)
{
    PM_ContinueWeaponAnim(0);
    pm->ps->weaponState = WEAPON_STATE_IDLE;
}

/* VERIFIED_DECOMPILER(0x340ab, 440ab_FUN_000440ab.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; raise/rechamber-bit gates, interrupt event, raise animation selection, delay calculation, and return paths checked against current decompiler output. */
qboolean PM_Weapon_CheckForRechamber(qboolean allowInterrupt)
{
    playerState_t *client = pm->ps;
    int weaponState;

    if (pml.weaponInfo->raiseEnabled == 0 ||
        Com_BitCheck(client->weaponRechamberBits,
                     client->currentWeapon) == 0) {
        return 0;
    }

    weaponState = client->weaponState;
    if (weaponState == WEAPON_STATE_RECHAMBERING && allowInterrupt != 0) {
        Com_BitClear(client->weaponRechamberBits,
                     client->currentWeapon);
        PM_AddEvent(EV_EJECT_BRASS);
        if (client->weaponTime != 0) {
            return 1;
        }
    }

    if (client->weaponTime == 0 ||
        ((weaponState != WEAPON_STATE_FIRING &&
          weaponState != WEAPON_STATE_RECHAMBERING &&
          weaponState != WEAPON_STATE_MELEE_WINDUP &&
          weaponState != WEAPON_STATE_MELEE_RELAX) &&
         client->weaponDelay == 0)) {
        if (weaponState == WEAPON_STATE_RECHAMBERING) {
            PM_Weapon_FinishRechamber();
        } else if (weaponState == WEAPON_STATE_IDLE) {
            if (client->adsFraction >
                PM_WEAPON_ADS_RAISE_THRESHOLD) {
                PM_StartWeaponAnim(PM_WEAPON_ANIM_RAISE_ADS);
            } else {
                PM_StartWeaponAnim(PM_WEAPON_ANIM_RAISE_HIP);
            }

            client->weaponState =
                WEAPON_STATE_RECHAMBERING;
            client->weaponTime =
                pml.weaponInfo->raiseTime;
            if (pml.weaponInfo->raiseInterruptTime == 0 ||
                pml.weaponInfo->raiseTime <=
                    pml.weaponInfo->raiseInterruptTime) {
                client->weaponDelay = 1;
            } else {
                client->weaponDelay =
                    pml.weaponInfo->raiseInterruptTime;
            }
            PM_AddEvent(EV_RECHAMBER_WEAPON);
        }
    }

    return 0;
}

/* VERIFIED_DECOMPILER(0x34320, 44320_FUN_00044320.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; reload-add delay selection, empty-reload timing, rechamber interrupt clamp, and weaponDelay stores checked against current decompiler output. */
void PM_SetReloadAddDelay(void)
{
    playerState_t *client = pm->ps;
    int weaponState = client->weaponState;
    int delay;

    if (weaponState == WEAPON_STATE_RELOAD_START ||
        weaponState == WEAPON_STATE_RELOAD_START_INTERRUPT) {
        int reloadStartAddTime = pml.weaponInfo->reloadStartAddTime;

        if (reloadStartAddTime == 0) {
            delay = 0;
        } else if (reloadStartAddTime <
                   pml.weaponInfo->reloadStartTime) {
            delay = reloadStartAddTime;
        } else {
            delay = pml.weaponInfo->reloadStartTime;
        }
    } else {
        int clipIndex = BG_ClipForWeapon(client->currentWeapon);

        if (client->clips[clipIndex] == 0 &&
            pml.weaponInfo->weaponType == WEAPTYPE_BULLET) {
            delay = pml.weaponInfo->reloadEmptyTime;
        } else {
            delay = pml.weaponInfo->reloadTime;
        }

        if (pml.weaponInfo->reloadAddTime != 0 &&
            pml.weaponInfo->reloadAddTime < delay) {
            delay = pml.weaponInfo->reloadAddTime;
        }
    }

    if (pml.weaponInfo->raiseEnabled != 0 &&
        Com_BitCheck(client->weaponRechamberBits,
                     client->currentWeapon) != 0) {
        if (delay == 0) {
            delay = client->weaponTime;
        }
        if (pml.weaponInfo->raiseInterruptTime <
            delay) {
            delay = pml.weaponInfo->raiseInterruptTime;
        }
        if (delay == 0) {
            delay = 1;
        }
        client->weaponDelay = delay;
        return;
    }

    if (delay != 0) {
        client->weaponDelay = delay;
    }
}

/* VERIFIED_DECOMPILER(0x3454b, 4454b_FUN_0004454b.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; empty/non-empty reload animation branches, reload events, loop-state transition, and delay helper call checked against current decompiler output. */
void PM_BeginReloadLoop(void)
{
    playerState_t *client = pm->ps;
    int clipIndex = BG_ClipForWeapon(client->currentWeapon);

    if (client->clips[clipIndex] == 0 &&
        pml.weaponInfo->weaponType == WEAPTYPE_BULLET) {
        PM_StartWeaponAnim(PM_WEAPON_ANIM_RELOAD_EMPTY);
        client->weaponTime =
            pml.weaponInfo->reloadEmptyTime;
        PM_AddEvent(EV_RELOAD_FROM_EMPTY);
    } else {
        PM_StartWeaponAnim(PM_WEAPON_ANIM_RELOAD);
        client->weaponTime =
            pml.weaponInfo->reloadTime;
        PM_AddEvent(EV_RELOAD);
    }

    if (client->weaponState ==
        WEAPON_STATE_RELOAD_START_INTERRUPT) {
        client->weaponState =
            WEAPON_STATE_RELOADING_INTERRUPT;
    } else {
        client->weaponState =
            WEAPON_STATE_RELOADING;
    }

    PM_SetReloadAddDelay();
}

/* VERIFIED_DECOMPILER(0x34659, 44659_FUN_00044659.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; reload-allowed states, weapon bounds, animscript gate, reload-start branch, events, and delay helper call checked against current decompiler output. */
void PM_BeginWeaponReload(void)
{
    playerState_t *client = pm->ps;
    int weaponState = client->weaponState;

    if ((weaponState == WEAPON_STATE_IDLE ||
         weaponState == WEAPON_STATE_FIRING ||
         weaponState == WEAPON_STATE_RECHAMBERING) &&
        client->currentWeapon != 0 &&
        client->currentWeapon <= BG_GetNumWeapons()) {
        if (BG_WeaponIsClipOnly(client->currentWeapon) == 0) {
            BG_AnimScriptEvent(client,
                               ANIM_EVENT_RELOAD, qfalse, qtrue);
        }

        if (pml.weaponInfo->segmentedReload == 0 ||
            pml.weaponInfo->reloadStartTime == 0) {
            PM_BeginReloadLoop();
        } else {
            PM_StartWeaponAnim(PM_WEAPON_ANIM_RELOAD_START);
            client->weaponTime =
                pml.weaponInfo->reloadStartTime;
            client->weaponState =
                WEAPON_STATE_RELOAD_START;
            PM_AddEvent(EV_RELOAD_START);
            PM_SetReloadAddDelay();
        }
    }
}

/* VERIFIED_DECOMPILER(0x347aa, 447aa_FUN_000447aa.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; ADS-in state guard, timing store, animation, event, and animscript call checked against current decompiler output. */
void PM_BeginAimDownSight(void)
{
    playerState_t *client = pm->ps;

    if (client->weaponState !=
        WEAPON_STATE_DEPLOYING) {
        client->weaponState =
            WEAPON_STATE_DEPLOYING;
        client->weaponTime =
            pml.weaponInfo->adsInTime;
        PM_StartWeaponAnim(PM_WEAPON_ANIM_ADS_IN);
        PM_AddEvent(EV_DEPLOY_WEAPON);
        BG_AnimScriptEvent(client,
                           ANIM_EVENT_LMG_DEPLOY, qfalse, qtrue);
    }
}

/* VERIFIED_DECOMPILER(0x3484c, 4484c_FUN_0004484c.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; ADS-out state guard, timing store, animation, event, and animscript call checked against current decompiler output. */
void PM_EndAimDownSight(void)
{
    playerState_t *client = pm->ps;

    if (client->weaponState !=
        WEAPON_STATE_BREAKING_DOWN) {
        client->weaponState =
            WEAPON_STATE_BREAKING_DOWN;
        client->weaponTime =
            pml.weaponInfo->adsOutTime;
        PM_StartWeaponAnim(PM_WEAPON_ANIM_ADS_OUT);
        PM_AddEvent(EV_BREAKDOWN_WEAPON);
        BG_AnimScriptEvent(client,
                           ANIM_EVENT_LMG_BREAKDOWN, qfalse, qtrue);
    }
}

/* VERIFIED_DECOMPILER(0x348ee, 448ee_FUN_000448ee.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; weapon bounds/ownership gates, ADS spread lockout, switch abort path, alt-switch detection, lower animations/events, slot update, and timing stores checked against current decompiler output. */
void PM_BeginWeaponChange(int currentWeapon, int nextWeapon)
{
    playerState_t *client = pm->ps;
    const weaponInfo_t *currentWeaponInfo;
    qboolean isAltSwitch;
    qboolean canPlayLowerAnim;
    int altSlot;

    if (nextWeapon < 0 || nextWeapon > BG_GetNumWeapons()) {
        return;
    }
    if (nextWeapon != 0 &&
        Com_BitCheck(client->weaponBits, nextWeapon) == 0) {
        return;
    }
    if (client->weaponState ==
        WEAPON_STATE_DROPPING) {
        return;
    }
    if ((pml.weaponInfo->weaponClass == WEAPCLASS_LMG ||
         pml.weaponInfo->weaponClass == WEAPCLASS_SPOTTER) &&
        ((client->playerStateFlags & PS_STANCE_FLAG_ADS) != 0 ||
         client->adsFraction > 0.0001f /* original float32 0x38d1b717 */)) {
        return;
    }

    client->weaponDelay = 0;

    if (currentWeapon == 0 ||
        Com_BitCheck(client->weaponBits, currentWeapon) == 0 ||
        client->grenadeTimeLeft > 0) {
        client->weaponTime = 0;
        client->weaponState =
            WEAPON_STATE_DROPPING;
        client->grenadeTimeLeft = 0;
        PM_SetProneMovementOverride();
        BG_AddPredictableEventToPlayerstate(EV_REMOVE_WEAPON_ATTACHMENTS,
                                            currentWeapon, client);
        return;
    }

    currentWeaponInfo = BG_GetInfoForWeapon(currentWeapon);
    (void)BG_GetInfoForWeapon(nextWeapon);

    isAltSwitch =
        nextWeapon != 0 &&
        nextWeapon == currentWeaponInfo->altWeapon;
    canPlayLowerAnim = 1;
    if (BG_WeaponIsClipOnly(currentWeapon) != 0 &&
        client->clips[BG_ClipForWeapon(currentWeapon)] == 0) {
        canPlayLowerAnim = 0;
    }

    client->grenadeTimeLeft = 0;
    if (isAltSwitch) {
        PM_AddEvent(EV_WEAPON_ALT);
        PM_StartWeaponAnim(PM_WEAPON_ANIM_ALT_SWITCH_LOWER);
    } else if (canPlayLowerAnim) {
        PM_AddEvent(EV_PUTAWAY_WEAPON);
        PM_StartWeaponAnim(PM_WEAPON_ANIM_LOWER);
    }

    if (!isAltSwitch) {
        BG_AnimScriptEvent(client,
                           ANIM_EVENT_DROP_WEAPON, qfalse, qfalse);
    }

    client->weaponState =
        WEAPON_STATE_DROPPING;
    PM_SetProneMovementOverride();

    if (isAltSwitch) {
        client->weaponTime =
            currentWeaponInfo->altSwitchLowerTime;
        altSlot = BG_IsPlayerWeaponInSlot(client, currentWeapon, 1);
        if (altSlot != 0) {
            BG_SetPlayerWeaponForSlot(client, altSlot, nextWeapon);
        }
    } else {
        client->weaponTime =
            currentWeaponInfo->lowerTime;
    }
}

/* VERIFIED_DECOMPILER(0x34c14, 44c14_FUN_00044c14.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; requested weapon validation, current weapon swap, zero/alt/non-alt raise branches, ADS reset, condition updates, events, aim-spread clamps, and return paths checked against current decompiler output. */
qboolean PM_Weapon_FinishWeaponChange(void)
{
    playerState_t *client = pm->ps;
    int requestedWeapon;
    int oldWeapon;
    const weaponInfo_t *newWeaponInfo;
    qboolean isAltSwitch;

    if (client->weaponState !=
        WEAPON_STATE_DROPPING) {
        return 0;
    }

    requestedWeapon = 0;
    if ((client->playerStateFlags & PS_STANCE_FLAG_SWITCH_BLOCKED) == 0 &&
        Com_BitCheck(client->weaponBits, pm->command.weapon) != 0) {
        if ((client->playerStateFlags & PS_STANCE_FLAG_WEAPON_DISABLED) == 0 &&
            Com_BitCheck(client->weaponBits, pm->command.weapon) != 0 &&
            ((client->entityStateFlags & PS_FLAG_IN_VEHICLE) == 0 ||
             (client->entityStateFlags & PS_FLAG_VEHICLE_ALLOW_WEAPON) != 0 ||
             BG_AllowPlayerWeaponAtVehiclePos(
                 client->vehicleType,
                 client->vehiclePosition) != 0)) {
            requestedWeapon = pm->command.weapon;
            if (requestedWeapon > BG_GetNumWeapons()) {
                requestedWeapon = 0;
            }
        }
    }
    if (Com_BitCheck(client->weaponBits, requestedWeapon) == 0) {
        requestedWeapon = 0;
    }

    oldWeapon = client->currentWeapon;
    client->currentWeapon = requestedWeapon;
    pml.weaponInfo = BG_GetInfoForWeapon(client->currentWeapon);

    if (oldWeapon == requestedWeapon) {
        client->weaponState =
            WEAPON_STATE_IDLE;
        PM_StartWeaponAnim(0);
        return 1;
    }

    client->weaponState =
        WEAPON_STATE_RAISING;
    if (oldWeapon == 0) {
        client->weaponTime =
            BG_GetInfoForWeapon(requestedWeapon)->switchRaiseTime;
        client->aimSpreadScale =
            PM_AIM_SPREAD_SCALE_MAX;
        PM_StartWeaponAnim(PM_WEAPON_ANIM_SWITCH_RAISE);
        PM_SetProneMovementOverride();
        return 1;
    }

    PM_SetProneMovementOverride();
    newWeaponInfo = BG_GetInfoForWeapon(requestedWeapon);
    if (newWeaponInfo->weaponClass == WEAPCLASS_LMG) {
        client->adsFraction = 0.0f;
    }

    isAltSwitch =
        requestedWeapon != 0 &&
        requestedWeapon == BG_GetInfoForWeapon(oldWeapon)->altWeapon;
    if (isAltSwitch) {
        client->weaponTime =
            BG_GetInfoForWeapon(requestedWeapon)->altSwitchRaiseTime;
    } else {
        PM_AddEvent(EV_RAISE_WEAPON);
        client->weaponTime =
            BG_GetInfoForWeapon(requestedWeapon)->switchRaiseTime;
    }

    BG_UpdateConditionValue(client->psClientNum, ANIM_COND_WEAPON,
                            requestedWeapon, 1);
    BG_UpdateConditionValue(
        client->psClientNum, ANIM_COND_WEAPONCLASS,
        BG_GetInfoForWeapon(requestedWeapon)->weaponClass, 1);

    if (!isAltSwitch) {
        BG_AnimScriptEvent(client,
                           ANIM_EVENT_RAISE_WEAPON, qfalse, qfalse);
        client->aimSpreadScale =
            PM_AIM_SPREAD_SCALE_MAX;
        PM_StartWeaponAnim(PM_WEAPON_ANIM_SWITCH_RAISE);
    } else {
        if (client->aimSpreadScale <
            PM_AIM_SPREAD_SCALE_ALT_SWITCH_MIN) {
            client->aimSpreadScale =
                PM_AIM_SPREAD_SCALE_ALT_SWITCH_MIN;
        }
        PM_StartWeaponAnim(PM_WEAPON_ANIM_ALT_SWITCH_RAISE);
    }

    return 1;
}

/* VERIFIED_DECOMPILER(0x35064, 45064_FUN_00045064.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; switch-raise state test, idle transition, animation clear, and boolean return checked against current decompiler output. */
qboolean PM_Weapon_FinishWeaponRaise(void)
{
    playerState_t *client = pm->ps;
    qboolean handled =
        client->weaponState ==
        WEAPON_STATE_RAISING;

    if (handled) {
        client->weaponState =
            WEAPON_STATE_IDLE;
        PM_StartWeaponAnim(0);
    }

    return handled;
}

/* VERIFIED_DECOMPILER(0x350c2, 450c2_FUN_000450c2.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; ADS-in finish state test, idle transition, animation clear, and boolean return checked against current decompiler output. */
qboolean PM_Weapon_FinishWeaponDeploy(void)
{
    playerState_t *client = pm->ps;
    qboolean handled =
        client->weaponState ==
        WEAPON_STATE_DEPLOYING;

    if (handled) {
        client->weaponState =
            WEAPON_STATE_IDLE;
        PM_StartWeaponAnim(0);
    }

    return handled;
}

/* VERIFIED_DECOMPILER(0x35120, 45120_FUN_00045120.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; ADS-out finish state test, idle transition, animation clear, and boolean return checked against current decompiler output. */
qboolean PM_Weapon_FinishWeaponBreakdown(void)
{
    playerState_t *client = pm->ps;
    qboolean handled =
        client->weaponState ==
        WEAPON_STATE_BREAKING_DOWN;

    if (handled) {
        client->weaponState =
            WEAPON_STATE_IDLE;
        PM_StartWeaponAnim(0);
    }

    return handled;
}

/* VERIFIED_DECOMPILER(0x3517e, 4517e_FUN_0004517e.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; reserve-ammo and clip-room gates, reloadRequiresAddRoom branches, reloadAmmoAdd comparisons, and boolean returns checked against current decompiler output. */
qboolean PM_Weapon_AllowReload(void)
{
    playerState_t *client = pm->ps;
    int weapon = client->currentWeapon;
    int clipIndex = BG_ClipForWeapon(weapon);
    int ammoIndex = BG_AmmoForWeapon(weapon);
    int clipSize;
    int reloadAmmoAdd;

    if (client->ammo[ammoIndex] == 0) {
        return 0;
    }

    clipSize = BG_GetAmmoClipSize(clipIndex);
    if (client->clips[clipIndex] >= clipSize) {
        return 0;
    }

    if (pml.weaponInfo->reloadRequiresAddRoom == 0) {
        return 1;
    }

    reloadAmmoAdd = pml.weaponInfo->reloadAmmoAdd;
    if (reloadAmmoAdd == 0 || clipSize <= reloadAmmoAdd) {
        return client->clips[clipIndex] == 0;
    }

    return reloadAmmoAdd <= clipSize - client->clips[clipIndex];
}

/* VERIFIED_DECOMPILER(0x352d2, 452d2_FUN_000452d2.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; rechamber-bit reload interrupt, raise-interrupt event, reload-start add-time zero path, fill timing selection, interrupt subtraction, and delayed fill/store paths checked against current decompiler output. */
void PM_Weapon_ReloadDelayedAction(void)
{
    playerState_t *client = pm->ps;
    int weaponState = client->weaponState;
    int fillTime;
    int interruptTime;

    if (pml.weaponInfo->raiseEnabled == 0 ||
        Com_BitCheck(client->weaponRechamberBits,
                     client->currentWeapon) == 0) {
        PM_FillCurrentWeaponClip();
        return;
    }

    Com_BitClear(client->weaponRechamberBits,
                 client->currentWeapon);
    PM_AddEvent(EV_EJECT_BRASS);

    if ((weaponState == WEAPON_STATE_RELOAD_START ||
         weaponState == WEAPON_STATE_RELOAD_START_INTERRUPT) &&
        pml.weaponInfo->reloadStartAddTime == 0) {
        return;
    }

    if (client->weaponTime == 0) {
        PM_FillCurrentWeaponClip();
        return;
    }

    if (weaponState == WEAPON_STATE_RELOAD_START ||
        weaponState == WEAPON_STATE_RELOAD_START_INTERRUPT) {
        fillTime = pml.weaponInfo->reloadStartAddTime;
        if (pml.weaponInfo->reloadStartTime <
            fillTime) {
            fillTime = pml.weaponInfo->reloadStartTime;
        }
    } else {
        int clipIndex = BG_ClipForWeapon(client->currentWeapon);

        if (client->clips[clipIndex] == 0 &&
            pml.weaponInfo->weaponType == WEAPTYPE_BULLET) {
            fillTime = pml.weaponInfo->reloadEmptyTime;
        } else {
            fillTime = pml.weaponInfo->reloadTime;
        }

        if (pml.weaponInfo->reloadAddTime != 0 &&
            pml.weaponInfo->reloadAddTime <
                fillTime) {
            fillTime = pml.weaponInfo->reloadAddTime;
        }
    }

    if (pml.weaponInfo->raiseInterruptTime <
        fillTime) {
        interruptTime = pml.weaponInfo->raiseInterruptTime;
    } else {
        interruptTime = 1;
    }

    if (fillTime - interruptTime < 1) {
        PM_FillCurrentWeaponClip();
    } else {
        client->weaponDelay =
            fillTime - interruptTime;
    }
}

/* VERIFIED_DECOMPILER(0x35564, 45564_FUN_00045564.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; reload-start/loop/end state machine, interrupt handling, clip and allow-reload tests, rechamber-bit clears, animation/event calls, and return values checked against current decompiler output. */
qboolean PM_Weapon_FinishReload(qboolean pendingInterrupt)
{
    playerState_t *client = pm->ps;
    int weaponState = client->weaponState;

    if (weaponState == WEAPON_STATE_RELOAD_START ||
        weaponState == WEAPON_STATE_RELOAD_START_INTERRUPT) {
        if (pendingInterrupt != 0) {
            PM_Weapon_ReloadDelayedAction();
        }

        if (client->weaponTime == 0) {
            int clipIndex = BG_ClipForWeapon(client->currentWeapon);

            if ((weaponState == WEAPON_STATE_RELOAD_START_INTERRUPT &&
                 client->clips[clipIndex] != 0) ||
                PM_Weapon_AllowReload() == 0) {
                Com_BitClear(
                    client->weaponRechamberBits,
                    client->currentWeapon);
                if (pml.weaponInfo->reloadEndTime == 0) {
                    client->weaponState =
                        WEAPON_STATE_IDLE;
                    PM_StartWeaponAnim(0);
                    return 0;
                }

                client->weaponState =
                    WEAPON_STATE_RELOAD_END;
                PM_StartWeaponAnim(PM_WEAPON_ANIM_RELOAD_END);
                client->weaponTime =
                    pml.weaponInfo->reloadEndTime;
                PM_AddEvent(EV_RELOAD_END);
            } else {
                PM_BeginReloadLoop();
            }
        }

        return 1;
    }

    if (weaponState == WEAPON_STATE_RELOAD_END) {
        client->weaponState =
            WEAPON_STATE_IDLE;
        PM_StartWeaponAnim(0);
    } else if (weaponState == WEAPON_STATE_RELOADING ||
               weaponState == WEAPON_STATE_RELOADING_INTERRUPT) {
        if (pendingInterrupt != 0) {
            PM_Weapon_ReloadDelayedAction();
            if (client->weaponTime != 0) {
                return 1;
            }
        }

        if (client->weaponTime == 0) {
            Com_BitClear(
                client->weaponRechamberBits,
                client->currentWeapon);
            if (pml.weaponInfo->segmentedReload == 0) {
                client->weaponState =
                    WEAPON_STATE_IDLE;
                PM_StartWeaponAnim(0);
            } else {
                if (weaponState != WEAPON_STATE_RELOADING_INTERRUPT &&
                    PM_Weapon_AllowReload() != 0) {
                    PM_BeginReloadLoop();
                    return 1;
                }

                if (pml.weaponInfo->reloadEndTime != 0) {
                    client->weaponState =
                        WEAPON_STATE_RELOAD_END;
                    PM_StartWeaponAnim(PM_WEAPON_ANIM_RELOAD_END);
                    client->weaponTime =
                        pml.weaponInfo->reloadEndTime;
                    PM_AddEvent(EV_RELOAD_END);
                    return 1;
                }

                client->weaponState =
                    WEAPON_STATE_IDLE;
                PM_StartWeaponAnim(0);
            }
        }

        if (pendingInterrupt != 0) {
            return 1;
        }
    }

    return 0;
}

/* VERIFIED_DECOMPILER(0x358b2, 458b2_FUN_000458b2.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; reload interrupt button edge, spread/sprint gates, reload-state animscript replay, manual reload, empty-clip auto-reload, prone movement gate, and begin-reload call checked against current decompiler output. */
void PM_Weapon_CheckForReload(void)
{
    playerState_t *client = pm->ps;
    int weaponState = client->weaponState;
    qboolean shouldBeginReload = 0;

    if (pml.weaponInfo->segmentedReload != 0 &&
        (weaponState == WEAPON_STATE_RELOAD_START ||
         weaponState == WEAPON_STATE_RELOADING) &&
        (pm->command.buttons &
                      PMOVE_WEAPON_INPUT_PRIMARY) &&
        !(pm->oldCommand.buttons & PMOVE_WEAPON_INPUT_PRIMARY)) {
        if (weaponState == WEAPON_STATE_RELOAD_START) {
            client->weaponState =
                WEAPON_STATE_RELOAD_START_INTERRUPT;
        } else {
            client->weaponState =
                WEAPON_STATE_RELOADING_INTERRUPT;
        }
        weaponState = client->weaponState;
    }

    /* Stock 0x359cb..0x359dc exits only for the ordered comparison
     * 0.99f > adsFraction; an unordered NaN continues into reload checks. */
    if ((pml.weaponInfo->weaponClass != WEAPCLASS_LMG ||
         ((client->playerStateFlags & PS_STANCE_FLAG_ADS) != 0 &&
          !(PM_WEAPON_RELOAD_ADS_FRACTION_MIN >
            client->adsFraction))) &&
        (client->playerStateFlags & PS_STANCE_FLAG_SPRINTING) == 0) {
        switch (weaponState) {
        case WEAPON_STATE_RAISING:
        case WEAPON_STATE_DROPPING:
        case WEAPON_STATE_MELEE_WINDUP:
        case WEAPON_STATE_MELEE_RELAX:
        case WEAPON_STATE_DEPLOYING:
        case WEAPON_STATE_BREAKING_DOWN:
            break;

        case WEAPON_STATE_RELOADING:
        case WEAPON_STATE_RELOADING_INTERRUPT:
        case WEAPON_STATE_RELOAD_START:
        case WEAPON_STATE_RELOAD_START_INTERRUPT:
        case WEAPON_STATE_RELOAD_END:
            if (pm->weaponAnimscriptEnabled != 0 &&
                BG_WeaponIsClipOnly(client->currentWeapon) == 0) {
                BG_AnimScriptEvent(client,
                                   ANIM_EVENT_RELOAD, qfalse, qtrue);
            }
            break;

        default: {
            int clipIndex = BG_ClipForWeapon(client->currentWeapon);
            int ammoIndex = BG_AmmoForWeapon(client->currentWeapon);

            if ((pm->command.wbuttons &
                              PMOVE_WEAPON_BUTTON_RELOAD) &&
                PM_Weapon_AllowReload() != 0) {
                shouldBeginReload = 1;
            }

            if (client->clips[clipIndex] == 0 &&
                client->ammo[ammoIndex] != 0 &&
                weaponState != WEAPON_STATE_FIRING &&
                ((client->playerStateFlags & PS_STANCE_FLAG_PRONE) == 0 ||
                 (pm->command.forwardmove == 0 &&
                  pm->command.rightmove == 0))) {
                shouldBeginReload = 1;
            }

            if (shouldBeginReload) {
                PM_BeginWeaponReload();
            }
            break;
        }
        }
    }
}

/* VERIFIED_DECOMPILER(0x35b66, 45b66_FUN_00045b66.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; spread/spotter ADS gate, ADS-input block, state switch returns, reload ADS hold, ADS-out animscript replay, and ADS begin/end calls checked against current decompiler output. */
void PM_Weapon_CheckForDeployBreakdown(void)
{
    playerState_t *client = pm->ps;
    int weaponClass = pml.weaponInfo->weaponClass;
    int weaponState;
    float adsFraction;

    if ((weaponClass == WEAPCLASS_LMG ||
         weaponClass == WEAPCLASS_SPOTTER) &&
        pm->adsInputBlocked == 0) {
        weaponState = client->weaponState;

        switch (weaponState) {
        case WEAPON_STATE_RAISING:
        case WEAPON_STATE_DROPPING:
        case WEAPON_STATE_MELEE_WINDUP:
        case WEAPON_STATE_MELEE_RELAX:
            return;

        case WEAPON_STATE_RELOADING:
        case WEAPON_STATE_RELOADING_INTERRUPT:
        case WEAPON_STATE_RELOAD_START:
        case WEAPON_STATE_RELOAD_START_INTERRUPT:
        case WEAPON_STATE_RELOAD_END:
            if ((client->playerStateFlags & PS_STANCE_FLAG_ADS) != 0) {
                return;
            }
            break;

        case WEAPON_STATE_BREAKING_DOWN:
            if (pm->weaponAnimscriptEnabled != 0) {
                BG_AnimScriptEvent(client,
                                   ANIM_EVENT_LMG_BREAKDOWN, qtrue, qtrue);
            }
            break;

        default:
            break;
        }

        adsFraction = client->adsFraction;
        if ((client->playerStateFlags & PS_STANCE_FLAG_ADS) == 0 ||
            adsFraction == 1.0f ||
            client->weaponState ==
                WEAPON_STATE_DEPLOYING) {
            if ((client->playerStateFlags & PS_STANCE_FLAG_ADS) == 0 &&
                adsFraction != 0.0f &&
                client->weaponState !=
                    WEAPON_STATE_BREAKING_DOWN) {
                PM_EndAimDownSight();
            }
        } else {
            PM_BeginAimDownSight();
        }
    }
}

/* VERIFIED_DECOMPILER(0x35ce8, 45ce8_FUN_00045ce8.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; clip-only non-spotter gate, empty clip/reserve tests, weapon removal, and empty event checked against current decompiler output. */
void PM_RemoveEmptyClipOnlyWeapon(void)
{
    playerState_t *client = pm->ps;
    int weapon = client->currentWeapon;

    if (BG_WeaponIsClipOnly(weapon) != 0 &&
        pml.weaponInfo->weaponClass != WEAPCLASS_SPOTTER) {
        int clipIndex = BG_ClipForWeapon(weapon);
        int ammoIndex = BG_AmmoForWeapon(weapon);

        if (client->clips[clipIndex] == 0 && client->ammo[ammoIndex] == 0) {
            BG_TakePlayerWeapon(client, weapon);
            PM_AddEvent(EV_NOAMMO);
        }
    }
}

/* VERIFIED_DECOMPILER(0x360fa, 460fa_FUN_000460fa.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; weaponTime decrement/hold gate, finish-rechamber and idle animation paths, weaponDelay decrement, expiration clamp, and boolean return checked against current decompiler output. */
qboolean PM_Weapon_WeaponTimeAdjust(void)
{
    playerState_t *client = pm->ps;
    int *weaponTime = &client->weaponTime;
    int *weaponDelay = &client->weaponDelay;

    if (*weaponTime != 0) {
        *weaponTime -= pml.msec;
        if (*weaponTime < 1) {
            if (pml.weaponInfo->weaponTimeHold !=
                    0 &&
                (pm->command.buttons &
                              PMOVE_WEAPON_INPUT_PRIMARY) &&
                client->currentWeapon == pm->command.weapon &&
                PM_WeaponAmmoAvailable(client->currentWeapon) != 0) {
                int weaponState;

                *weaponTime = 1;
                weaponState = client->weaponState;
                if (weaponState == WEAPON_STATE_RECHAMBERING) {
                    PM_Weapon_FinishRechamber();
                } else if (weaponState == WEAPON_STATE_FIRING ||
                           weaponState == WEAPON_STATE_MELEE_WINDUP ||
                           weaponState == WEAPON_STATE_MELEE_RELAX) {
                    PM_ContinueWeaponAnim(0);
                    client->weaponState =
                        WEAPON_STATE_IDLE;
                }
            } else {
                *weaponTime = 0;
            }
        }
    }

    if (*weaponDelay == 0) {
        return 0;
    }

    *weaponDelay -= pml.msec;
    if (*weaponDelay > 0) {
        return 0;
    }

    *weaponDelay = 0;
    return 1;
}

/* VERIFIED_DECOMPILER(0x362ea, 462ea_FUN_000462ea.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; grenade special-time gate, weapon-time/state gate, switch-blocked/disabled/vehicle branches, requested weapon validation, missing-current weapon drop, and begin-change arguments checked against current decompiler output. */
void PM_Weapon_CheckForChangeWeapon(void)
{
    playerState_t *client = pm->ps;
    int weaponState = client->weaponState;
    int weaponTime = client->weaponTime;
    int weaponDelay = client->weaponDelay;
    int specialTime = client->grenadeTimeLeft;
    int currentWeapon = client->currentWeapon;
    int requestedWeapon = pm->command.weapon;

    if (pml.weaponInfo->weaponType ==
            WEAPTYPE_GRENADE &&
        pml.weaponInfo->specialTimeEnabled != 0 &&
        specialTime < pml.weaponInfo->specialTimeThreshold &&
        specialTime != 0 &&
        Com_BitCheck(client->weaponBits, currentWeapon) != 0) {
        return;
    }

    if (weaponTime != 0 &&
        weaponState != WEAPON_STATE_RELOADING &&
        weaponState != WEAPON_STATE_RELOAD_START &&
        weaponState != WEAPON_STATE_RELOAD_END &&
        weaponState != WEAPON_STATE_RELOAD_START_INTERRUPT &&
        weaponState != WEAPON_STATE_RELOADING_INTERRUPT &&
        weaponState != WEAPON_STATE_RECHAMBERING &&
        (weaponState == WEAPON_STATE_FIRING ||
         weaponState == WEAPON_STATE_MELEE_WINDUP ||
         weaponState == WEAPON_STATE_MELEE_RELAX ||
         weaponDelay != 0)) {
        return;
    }

    if ((client->playerStateFlags & PS_STANCE_FLAG_SWITCH_BLOCKED) != 0) {
        if (currentWeapon != 0) {
            PM_BeginWeaponChange(currentWeapon, 0);
        }
        return;
    }

    if ((client->playerStateFlags & PS_STANCE_FLAG_WEAPON_DISABLED) != 0 ||
        (((client->entityStateFlags & PS_FLAG_IN_VEHICLE) != 0 &&
          (client->entityStateFlags & PS_FLAG_VEHICLE_ALLOW_WEAPON) == 0) &&
         BG_AllowPlayerWeaponAtVehiclePos(
             client->vehicleType,
             client->vehiclePosition) == 0)) {
        if (currentWeapon != 0) {
            PM_BeginWeaponChange(currentWeapon, 0);
        }
        return;
    }

    if (currentWeapon == requestedWeapon ||
        ((client->playerStateFlags & PS_STANCE_FLAG_KEEP_CURRENT_WEAPON) != 0 &&
         currentWeapon != 0) ||
        (requestedWeapon != 0 &&
         Com_BitCheck(client->weaponBits, requestedWeapon) == 0)) {
        if (currentWeapon != 0 &&
            Com_BitCheck(client->weaponBits, currentWeapon) == 0) {
            PM_BeginWeaponChange(currentWeapon, 0);
        }
        return;
    }

    PM_BeginWeaponChange(currentWeapon, requestedWeapon);
}

/* VERIFIED_DECOMPILER(0x366b2, 466b2_FUN_000466b2.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; grenade special-time hold gate, primary-release/delay branch, fire animation clear, idle transition, and return values checked against current decompiler output. */
qboolean PM_Weapon_FinishFiring(qboolean delayExpired)
{
    playerState_t *client = pm->ps;
    qboolean primaryHeld =
        (pm->command.buttons &
                      PMOVE_WEAPON_INPUT_PRIMARY);
    int specialTime = client->grenadeTimeLeft;

    if (!primaryHeld &&
        pml.weaponInfo->weaponType ==
            WEAPTYPE_GRENADE &&
        pml.weaponInfo->specialTimeEnabled != 0 &&
        specialTime <
            pml.weaponInfo->specialTimeThreshold &&
        specialTime != 0) {
        return 0;
    }

    if (!primaryHeld && delayExpired == 0) {
        if (client->weaponState ==
            WEAPON_STATE_FIRING) {
            PM_ContinueWeaponAnim(0);
        }
        client->weaponState =
            WEAPON_STATE_IDLE;
        return 1;
    }

    return 0;
}

/* VERIFIED_DECOMPILER(0x3679b, 4679b_FUN_0004679b.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; original grenade/non-grenade fire setup, delay/time stores, ADS fire delay rounding, animscript call, rechamber-bit set, fire state, and prone override checked. */
void PM_Weapon_StartFiring(qboolean delayExpired)
{
    playerState_t *client = pm->ps;

    if (pml.weaponInfo->weaponType ==
        WEAPTYPE_GRENADE) {
        if (delayExpired == 0 &&
            client->grenadeTimeLeft == 0) {
            if (PM_WeaponAmmoAvailable(client->currentWeapon) != 0) {
                client->grenadeTimeLeft =
                    pml.weaponInfo->specialTimeThreshold;
                PM_StartWeaponAnim(PM_WEAPON_ANIM_SPECIAL_FIRE);
                PM_AddEvent(EV_PULLBACK_WEAPON);
            }

            client->weaponDelay =
                pml.weaponInfo->specialFireDelay;
            client->weaponTime = 0;
        }
    } else {
        client->weaponDelay =
            pml.weaponInfo->fireDelay;
        client->weaponTime =
            pml.weaponInfo->fireTime;

        if (pml.weaponInfo->adsFireDelayEnabled != 0) {
            static const float one = 1.0f;
            float rate = pml.weaponInfo->adsFireDelayRate;
            float adsFraction =
                client->adsFraction;

            /* (1/rate) * (1-adsFraction) kept 80-bit, fistp-direct trunc. */
#if EMULATE_X87
            client->weaponDelay = x87f_store_i32_trunc(x87f_mul(
                x87f_div(x87f_load_f32(one), x87f_load_f32(rate)),
                x87f_sub(x87f_load_f32(one), x87f_load_f32(adsFraction))));
#elif defined(__x86_64__)
            client->weaponDelay = CODUO_X87_TRUNCATE_I32(
                ((long double)one / (long double)rate) *
                ((long double)one - (long double)adsFraction));
#else
            client->weaponDelay =
                (int32_t)((one / rate) * (one - adsFraction));
#endif
        }

        BG_AnimScriptEvent(client,
                           ANIM_EVENT_FIRE_WEAPON, qfalse, qtrue);
        if (pml.weaponInfo->raiseEnabled != 0) {
            Com_BitSet(client->weaponRechamberBits,
                       client->currentWeapon);
        }
    }

    client->weaponState =
        WEAPON_STATE_FIRING;
    PM_SetProneMovementOverride();
}

/* VERIFIED_DECOMPILER(0x36997, 46997_FUN_00046997.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; constant one-ammo return checked against current decompiler output. */
int PM_Weapon_GetAmmoRequired(int weapon)
{
    (void)weapon;

    return 1;
}

/* VERIFIED_DECOMPILER(0x369a1, 469a1_FUN_000469a1.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; ammo-required call, clip availability test, reserve-ammo branch, empty event gate, reload start, no-ammo animation clear/time penalty, and boolean returns checked against current decompiler output. */
qboolean PM_Weapon_CheckFiringAmmo(void)
{
    playerState_t *client = pm->ps;
    int weapon = client->currentWeapon;
    int ammoCost = PM_Weapon_GetAmmoRequired(weapon);

    if (PM_WeaponAmmoAvailable(weapon) >= ammoCost) {
        return 1;
    }

    int ammoIndex = BG_AmmoForWeapon(weapon);
    qboolean reserveHasAmmo = client->ammo[ammoIndex] >= ammoCost;

    if (pml.weaponInfo->weaponType !=
            WEAPTYPE_GRENADE &&
        !reserveHasAmmo &&
        pml.weaponInfo->weaponClass != WEAPCLASS_SPOTTER) {
        PM_AddEvent(EV_NOAMMO);
    }

    if (reserveHasAmmo) {
        PM_BeginWeaponReload();
    } else {
        PM_ContinueWeaponAnim(0);
        client->weaponTime +=
            PM_WEAPON_NO_AMMO_TIME_PENALTY;
    }

    return 0;
}

/* VERIFIED_DECOMPILER(0x36acc, 46acc_FUN_00046acc.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; ADS threshold, clip-empty animation selection, and PM_StartWeaponAnim argument checked against current decompiler output. */
void PM_Weapon_SetFPSFireAnim(void)
{
    playerState_t *client = pm->ps;
    qboolean clipEmpty = PM_WeaponClipEmpty(client->currentWeapon);
    uint32_t anim;

    if (client->adsFraction >
        PM_WEAPON_ADS_RAISE_THRESHOLD) {
        anim = clipEmpty ? PM_WEAPON_ANIM_ADS_FIRE_LASTSHOT :
                           PM_WEAPON_ANIM_ADS_FIRE;
    } else {
        anim = clipEmpty ? PM_WEAPON_ANIM_FIRE_LASTSHOT :
                           PM_WEAPON_ANIM_FIRE;
    }

    PM_StartWeaponAnim(anim);
}

/* VERIFIED_DECOMPILER(0x36b6a, 46b6a_FUN_00046b6a.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; full-ADS early return, fireAimSpreadScale add, 255.0 clamp, and aimSpreadScale store checked against current decompiler output. */
void PM_Weapon_AddFiringAimSpreadScale(void)
{
    playerState_t *client = pm->ps;
    float *aimSpreadScale = &client->aimSpreadScale;

    if (client->adsFraction == 1.0f) {
        return;
    }

    *aimSpreadScale +=
        pml.weaponInfo->fireAimSpreadScale *
        PM_AIM_SPREAD_SCALE_MAX;
    if (*aimSpreadScale > PM_AIM_SPREAD_SCALE_MAX) {
        *aimSpreadScale = PM_AIM_SPREAD_SCALE_MAX;
    }
}

/* VERIFIED_DECOMPILER(0x36c0e, 46c0e_FUN_00046c0e.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; sprint/spotter/spread gates, start/check firing calls, ammo-use and gas double-use branches, vehicle fire-position gate, grenade/gas fire-time stores, fire event selection, spread-scale add, and clip-only removal checked against current decompiler output. */
void PM_Weapon_FireWeapon(qboolean delayExpired)
{
    playerState_t *client = pm->ps;
    int weapon = client->currentWeapon;
    int weaponClass =
        pml.weaponInfo->weaponClass;

    if ((client->playerStateFlags & PS_STANCE_FLAG_SPRINTING) != 0) {
        return;
    }

    if (weaponClass == WEAPCLASS_SPOTTER &&
        ((client->playerStateFlags & PS_STANCE_FLAG_ADS) == 0 ||
         pml.weaponInfo->adsFireDelayEnabled == 0)) {
        return;
    }

    if (weaponClass == WEAPCLASS_LMG &&
        ((client->playerStateFlags & PS_STANCE_FLAG_ADS) == 0 ||
         client->adsFraction <
             PM_WEAPON_RELOAD_ADS_FRACTION_MIN)) {
        return;
    }

    PM_Weapon_StartFiring(delayExpired);
    if (PM_Weapon_CheckFiringAmmo() == 0 ||
        client->weaponDelay != 0) {
        return;
    }

    if (PM_WeaponAmmoAvailable(weapon) != -1 &&
        ((client->entityStateFlags & PS_FLAG_FIRE_POSITION_CHECK_MASK) == 0 ||
         BG_AllowPlayerWeaponAtVehiclePos(
             client->vehicleType,
             client->vehiclePosition) != 0)) {
        PM_WeaponUseAmmo(weapon, PM_Weapon_GetAmmoRequired(weapon));
        if (pml.weaponInfo->weaponType ==
            WEAPTYPE_GAS) {
            PM_WeaponUseAmmo(weapon, PM_Weapon_GetAmmoRequired(weapon));
        }
    }

    if (pml.weaponInfo->weaponType ==
        WEAPTYPE_GRENADE) {
        client->weaponTime =
            pml.weaponInfo->fireTime;
    }
    if (pml.weaponInfo->weaponType ==
        WEAPTYPE_GAS) {
        client->weaponTime =
            pml.weaponInfo->fireTime;
    }

    PM_Weapon_SetFPSFireAnim();
    PM_AddEvent(PM_WeaponClipEmpty(weapon) != 0 ? EV_FIRE_WEAPON_LASTSHOT :
                                                  EV_FIRE_WEAPON);
    PM_Weapon_AddFiringAimSpreadScale();
    PM_RemoveEmptyClipOnlyWeapon();
}

/* VERIFIED_DECOMPILER(0x36e91, 46e91_FUN_00046e91.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; melee minimum weaponTime clamp, relax event, relax state store, and prone override checked against current decompiler output. */
void PM_Weapon_FireMelee(void)
{
    playerState_t *client = pm->ps;
    int minimumTime =
        pml.weaponInfo->meleeTime -
        pml.weaponInfo->meleeWindup;
    int *weaponTime = &client->weaponTime;

    if (*weaponTime < minimumTime) {
        *weaponTime = minimumTime;
    }

    PM_AddEvent(EV_FIRE_MELEE);
    client->weaponState =
        WEAPON_STATE_MELEE_RELAX;
    PM_SetProneMovementOverride();
}

/* VERIFIED_DECOMPILER(0x36f38, 46f38_FUN_00046f38.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; melee windup/relax state handling, fire-melee call, idle animation clear, state clear, and return values checked against current decompiler output. */
qboolean PM_Weapon_FinishMelee(void)
{
    playerState_t *client = pm->ps;
    int weaponState = client->weaponState;

    if (weaponState == WEAPON_STATE_MELEE_WINDUP) {
        PM_Weapon_FireMelee();
        return 1;
    }

    if (weaponState == WEAPON_STATE_MELEE_RELAX) {
        PM_ContinueWeaponAnim(0);
        client->weaponState =
            WEAPON_STATE_IDLE;
        return 1;
    }

    return 0;
}

/* VERIFIED_DECOMPILER(0x36fb7, 46fb7_FUN_00046fb7.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; melee damage/ADS/gas/delay gates, reload-state delay exception, melee-held flag clear/set, sprint gate, blocked states, animscript/animation/event calls, immediate fire path, timing stores, state store, and prone override checked against current decompiler output. */
void PM_Weapon_CheckForMelee(qboolean delayExpired)
{
    playerState_t *client = pm->ps;
    int weaponState = client->weaponState;

    if (pml.weaponInfo->meleeDamage == 0) {
        return;
    }

    if ((client->playerStateFlags & PS_STANCE_FLAG_ADS) != 0 &&
        ((const weaponInfo_t *)BG_GetInfoForWeapon(client->currentWeapon))->weaponClass ==
            WEAPCLASS_LMG) {
        return;
    }

    if (weaponState == WEAPON_STATE_BREAKING_DOWN) {
        return;
    }

    if (pml.weaponInfo->weaponType ==
            WEAPTYPE_GRENADE &&
        ((pm->command.buttons &
                       PMOVE_WEAPON_INPUT_PRIMARY) ||
         weaponState == WEAPON_STATE_FIRING)) {
        return;
    }

    if (delayExpired != 0) {
        return;
    }

    if (client->weaponDelay != 0 &&
        weaponState != WEAPON_STATE_RELOADING &&
        weaponState != WEAPON_STATE_RELOADING_INTERRUPT &&
        weaponState != WEAPON_STATE_RELOAD_START &&
        weaponState != WEAPON_STATE_RELOAD_START_INTERRUPT &&
        weaponState != WEAPON_STATE_RELOAD_END) {
        return;
    }

    if (!(pm->command.buttons &
                       PMOVE_WEAPON_INPUT_MELEE)) {
        client->playerStateFlags &= ~PS_STANCE_FLAG_MELEE_HELD;
        return;
    }

    if ((client->playerStateFlags & PS_STANCE_FLAG_MELEE_HELD) != 0 ||
        (client->playerStateFlags & PS_STANCE_FLAG_SPRINTING) != 0) {
        return;
    }

    client->playerStateFlags |= PS_STANCE_FLAG_MELEE_HELD;
    if (weaponState >= WEAPON_STATE_RAISING &&
        (weaponState <= WEAPON_STATE_DROPPING ||
         weaponState == WEAPON_STATE_MELEE_WINDUP ||
         weaponState == WEAPON_STATE_MELEE_RELAX)) {
        return;
    }

    BG_AnimScriptEvent(client,
                       ANIM_EVENT_MELEE_ATTACK, qfalse, qtrue);
    PM_StartWeaponAnim(PM_WEAPON_ANIM_MELEE);
    PM_AddEvent(EV_MELEE_SWIPE);

    if (pml.weaponInfo->meleeWindup == 0) {
        PM_Weapon_FireMelee();
        return;
    }

    client->weaponTime =
        pml.weaponInfo->meleeTime;
    client->weaponDelay =
        pml.weaponInfo->meleeWindup;
    client->weaponState =
        WEAPON_STATE_MELEE_WINDUP;
    PM_SetProneMovementOverride();
}

/* VERIFIED_DECOMPILER(0x3726c, 4726c_PM_Weapon.c, VERIFY-ITEMS-WEAPON-PM-2026-06-17): DATAFLOW_VERIFIED; weapon-disabled and pmType exits, repaired prone/crouch fire-position early return, vehicle reset path, debug print gates, ADS lerp, grenade special-fire timer/events/ammo use, helper call order, aim-spread maxing, finish-chain order, and fire fallback checked against current decompiler output. */
void PM_Weapon(void)
{
    playerState_t *client = pm->ps;
    const uint32_t proneOrCrouchFirePositionFlags =
        PS_FLAG_FIRE_POSITION_CHECK_MASK & ~PS_FLAG_IN_VEHICLE;
    qboolean delayExpired;
    qboolean pendingRaiseHandled;

    if ((client->playerStateFlags & PS_STANCE_FLAG_WEAPON_DISABLED) != 0) {
        return;
    }

    if (client->pmType >= PM_TYPE_ANIM_SCRIPT_LIMIT) {
        client->currentWeapon = 0;
        return;
    }

    if ((client->entityStateFlags & proneOrCrouchFirePositionFlags) != 0) {
        return;
    }

    if ((client->entityStateFlags & PS_FLAG_IN_VEHICLE) != 0 &&
        BG_AllowPlayerWeaponAtVehiclePos(client->vehicleType,
                                         client->vehiclePosition) == 0) {
        PM_UpdateAimDownSightLerp();
        if (client->weaponState != WEAPON_STATE_IDLE) {
            PM_StartWeaponAnim(0);
        }
        client->weaponTime = 0;
        client->weaponDelay = 0;
        client->weaponState = WEAPON_STATE_IDLE;
        PM_ContinueWeaponAnim(0);
        return;
    }

    if (bg_debugWeaponState.integer != 0 &&
        bg_debugWeaponState.integer != 2) {
        PM_Weapon_PrintWeaponState();
    }
    if (bg_debugWeaponAnim.integer != 0 &&
        bg_debugWeaponAnim.integer != 2) {
        PM_Weapon_PrintWeaponAnim();
    }

    PM_UpdateAimDownSightLerp();

    if (pml.weaponInfo->weaponType ==
        WEAPTYPE_GRENADE) {
        if (pml.weaponInfo->specialTimeEnabled != 0 &&
            (((pm->command.buttons &
               PMOVE_WEAPON_INPUT_SPECIAL_FIRE) != 0 &&
              ((pm->oldCommand.buttons &
                PMOVE_WEAPON_INPUT_SPECIAL_FIRE) == 0)) ||
             ((pm->command.buttons &
               PMOVE_WEAPON_INPUT_SPECIAL_FIRE) == 0 &&
              ((pm->oldCommand.buttons &
                PMOVE_WEAPON_INPUT_SPECIAL_FIRE) != 0))) &&
            client->grenadeTimeLeft >=
                pml.weaponInfo->specialTimeThreshold) {
            client->grenadeTimeLeft -= 10;
            PM_AddEvent(EV_GRENADE_SPOON);
        }

        if (client->grenadeTimeLeft > 0) {
            if (pml.weaponInfo->specialTimeEnabled != 0 &&
                client->grenadeTimeLeft <
                    pml.weaponInfo->specialTimeThreshold) {
                client->grenadeTimeLeft -= pml.msec;
            }

            if (client->grenadeTimeLeft < 51) {
                client->grenadeTimeLeft = 50;
                PM_AddEvent(EV_FIRE_WEAPON);
                /* Grenades with splash damage emit the extra special-fire event. */
                if (pml.weaponInfo->missileSplashDamage != 0) {
                    PM_AddEvent(EV_GRENADE_SUICIDE);
                }
                PM_WeaponUseAmmo(client->currentWeapon, 1);
                client->weaponDelay = 0;
                client->weaponTime = 1600;
                PM_RemoveEmptyClipOnlyWeapon();
                return;
            }

            if ((pm->command.buttons &
                 PMOVE_WEAPON_INPUT_PRIMARY) != 0 &&
                client->weaponDelay <= pml.msec) {
                client->weaponDelay = pml.msec + 1;
            }
            if ((pm->command.buttons &
                 PMOVE_WEAPON_INPUT_PRIMARY) == 0 &&
                client->weaponDelay <= pml.msec) {
                BG_AnimScriptEvent(client,
                                   ANIM_EVENT_FIRE_WEAPON, qfalse, qtrue);
            }
        }
    }

    delayExpired = PM_Weapon_WeaponTimeAdjust();
    PM_Weapon_CheckForDeployBreakdown();
    PM_Weapon_CheckForChangeWeapon();
    PM_Weapon_CheckForReload();
    PM_Weapon_CheckForMelee(delayExpired);

    pendingRaiseHandled = PM_Weapon_CheckForRechamber(delayExpired);
    if (pendingRaiseHandled == 0) {
        if (((client->playerStateFlags & PS_STANCE_FLAG_PRONE) != 0 &&
             (pm->command.forwardmove != 0 ||
              pm->command.rightmove != 0)) ||
            ((client->playerStateFlags & PS_STANCE_FLAG_SPRINTING) != 0 &&
             (pm->command.forwardmove != 0 ||
              pm->command.rightmove != 0)) ||
            client->weaponState == WEAPON_STATE_MELEE_WINDUP ||
            client->weaponState == WEAPON_STATE_MELEE_RELAX) {
            client->aimSpreadScale = PM_AIM_SPREAD_SCALE_MAX;
        }

        if ((delayExpired != 0 ||
             (client->weaponTime == 0 && client->weaponDelay == 0)) &&
            PM_Weapon_FinishReload(delayExpired) == 0 &&
            PM_Weapon_FinishMelee() == 0 &&
            PM_Weapon_FinishWeaponChange() == 0 &&
            PM_Weapon_FinishWeaponRaise() == 0 &&
            PM_Weapon_FinishWeaponDeploy() == 0 &&
            PM_Weapon_FinishWeaponBreakdown() == 0 &&
            client->currentWeapon != 0 &&
            PM_Weapon_FinishFiring(delayExpired) == 0) {
            PM_Weapon_FireWeapon(delayExpired);
        }
    }
}

/* VERIFIED_DECOMPILER(0x377cc, 477cc_FUN_000477cc.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; state-change guard, pm commandTime/prefix prints, last-state store, and debug-name switch strings checked against current decompiler output. */
void PM_Weapon_PrintWeaponState(void)
{
    int weaponState =
        pm->ps->weaponState;

    if (PMDebugLastWeaponState == weaponState) {
        return;
    }

    Com_Printf(" %i %s_", pm->command.commandTime, PMDebugPrefix);
    PMDebugLastWeaponState = weaponState;
    Com_Printf("WEAP_STATE -- ");
    switch (weaponState) {
    case WEAPON_STATE_IDLE:
        Com_Printf("WEAPON_READY\n");
        break;
    case WEAPON_STATE_RAISING:
        Com_Printf("WEAPON_RAISING\n");
        break;
    case WEAPON_STATE_DROPPING:
        Com_Printf("WEAPON_DROPPING\n");
        break;
    case WEAPON_STATE_FIRING:
        Com_Printf("WEAPON_FIRING\n");
        break;
    case WEAPON_STATE_RECHAMBERING:
        Com_Printf("WEAPON_RECHAMBERING\n");
        break;
    case WEAPON_STATE_RELOADING:
        Com_Printf("WEAPON_RELOADING\n");
        break;
    case WEAPON_STATE_RELOADING_INTERRUPT:
        Com_Printf("WEAPON_RELOADING_INTERUPT\n");
        break;
    case WEAPON_STATE_RELOAD_START:
        Com_Printf("WEAPON_RELOAD_START\n");
        break;
    case WEAPON_STATE_RELOAD_START_INTERRUPT:
        Com_Printf("WEAPON_RELOAD_START_INTERUPT\n");
        break;
    case WEAPON_STATE_RELOAD_END:
        Com_Printf("WEAPON_RELOAD_END\n");
        break;
    case WEAPON_STATE_MELEE_WINDUP:
        Com_Printf("WEAPON_MELEE_WINDUP\n");
        break;
    case WEAPON_STATE_MELEE_RELAX:
        Com_Printf("WEAPON_MELEE_RELAX\n");
        break;
    default:
        Com_Printf("UNKNOWN\n");
        break;
    }
}

/* VERIFIED_DECOMPILER(0x3793a, 4793a_FUN_0004793a.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; weaponAnim mask 0xfffffdff, print/store order, and direct state-specific Com_Printf switch checked against current machine code. */
void PM_Weapon_PrintWeaponAnim(void)
{
    uint32_t weaponAnim = pm->ps->weaponAnim & WEAPON_ANIM_INDEX_MASK;

    Com_Printf(" %i %s_", pm->command.commandTime, PMDebugPrefix);
    PMDebugLastWeaponAnim = weaponAnim;
    Com_Printf("WEAP_ANIM -- ");
    switch (weaponAnim) {
    case PM_WEAPON_ANIM_IDLE:
        Com_Printf("WEAP_IDLE\n");
        break;
    case PM_WEAPON_ANIM_FIRE:
        Com_Printf("WEAP_ATTACK\n");
        break;
    case PM_WEAPON_ANIM_FIRE_LASTSHOT:
        Com_Printf("WEAP_ATTACK_LASTSHOT\n");
        break;
    case PM_WEAPON_ANIM_RAISE_HIP:
        Com_Printf("WEAP_RECHAMBER\n");
        break;
    case PM_WEAPON_ANIM_ADS_FIRE:
        Com_Printf("WEAP_ADS_ATTACK\n");
        break;
    case PM_WEAPON_ANIM_ADS_FIRE_LASTSHOT:
        Com_Printf("WEAP_ADS_ATTACK_LASTSHOT\n");
        break;
    case PM_WEAPON_ANIM_RAISE_ADS:
        Com_Printf("WEAP_ADS_RECHAMBER\n");
        break;
    case PM_WEAPON_ANIM_MELEE:
        Com_Printf("WEAP_MELEE_ATTACK\n");
        break;
    case PM_WEAPON_ANIM_LOWER:
        Com_Printf("WEAP_DROP\n");
        break;
    case PM_WEAPON_ANIM_SWITCH_RAISE:
        Com_Printf("WEAP_RAISE\n");
        break;
    case PM_WEAPON_ANIM_RELOAD:
        Com_Printf("WEAP_RELOAD\n");
        break;
    case PM_WEAPON_ANIM_RELOAD_EMPTY:
        Com_Printf("WEAP_RELOAD_EMPTY\n");
        break;
    case PM_WEAPON_ANIM_RELOAD_START:
        Com_Printf("WEAP_RELOAD_START\n");
        break;
    case PM_WEAPON_ANIM_RELOAD_END:
        Com_Printf("WEAP_RELOAD_END\n");
        break;
    case PM_WEAPON_ANIM_ALT_SWITCH_LOWER:
        Com_Printf("WEAP_ALTSWITCHFROM\n");
        break;
    case PM_WEAPON_ANIM_ALT_SWITCH_RAISE:
        Com_Printf("WEAP_ALTSWITCHTO\n");
        break;
    case PM_WEAPON_ANIM_DEPLOYED:
        Com_Printf("WEAP_DEPLOYED\n");
        break;
    case PM_WEAPON_ANIM_ADS_IN:
        Com_Printf("WEAP_DEPLOY\n");
        break;
    case PM_WEAPON_ANIM_ADS_OUT:
        Com_Printf("WEAP_BREAKDOWN\n");
        break;
    default:
        Com_Printf("UNKNOWN\n");
        break;
    }
}

/* VERIFIED_DECOMPILER(0x37cfb, 47cfb_FUN_00047cfb.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; sprint/prone/crouch/stand rot thresholds and targets, ADS target scaling, frameTime interpolation/min step, and final ADS apply gate checked against current decompiler output. */
void BG_CalculateWeaponPosition_BasePosition_angles(
    pm_weapon_angle_state_t *state,
    vec3_t angles)
{
    gclient_t *client = state->client;
    const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(client->ps.currentWeapon);
    float threshold = game_compat_weapon_move_threshold(client, weaponInfo);
    vec3_t target;
    int i;

    if (state->speed <= threshold ||
        client->ps.weaponState ==
            WEAPON_STATE_RELOADING) {
        target[0] = 0.0f;
        target[1] = 0.0f;
        target[2] = 0.0f;
    } else {
        /* Original i386 0x37db9..0x37dca filds ps.speed straight into the
         * 80-bit subtract; a (float) cast of the int would add a rounding.
         * (speed-threshold) / (fild(ps.speed)-threshold), one store -> shim. */
#if EMULATE_X87
        float fraction = x87f_store_f32(x87f_div(
            x87f_sub(x87f_load_f32(state->speed), x87f_load_f32(threshold)),
            x87f_sub(x87f_load_i32(client->ps.speed), x87f_load_f32(threshold))));
#else
        float fraction =
            (state->speed - threshold) /
            ((long double)client->ps.speed -
             threshold);
#endif

        if (fraction < 0.0f) {
            fraction = 0.0f;
        } else if (fraction > 1.0f) {
            fraction = 1.0f;
        }

        game_compat_weapon_move_target_offsets(client, weaponInfo, fraction, target);
    }

    if (client->ps.adsFraction != 0.0f) {
        float scale =
            1.0f - client->ps.adsFraction;

        target[0] *= scale;
        target[1] *= scale;
        target[2] *= scale;
    }

    for (i = 0; i < 3; ++i) {
        if (state->moveOffset[i] != target[i]) {
            float step;

            /* (target-moveOffset)*frameTime*rotRate kept 80-bit, one store; the
             * min-step clamp compares step vs the 80-bit frameTime*0.1 product
             * (single mul, full-width fucompp) -> shim. */
            float rotRate = (client->ps.viewHeightCurrent ==
                             (long double)client->ps.proneViewHeight)
                                ? weaponInfo->posProneRotRate
                                : weaponInfo->posRotRate;
#if EMULATE_X87
            step = x87f_store_f32(x87f_mul(
                x87f_mul(x87f_sub(x87f_load_f32(target[i]),
                                  x87f_load_f32(state->moveOffset[i])),
                         x87f_load_f32(state->frameTime)),
                x87f_load_f32(rotRate)));
#else
            step = (target[i] - state->moveOffset[i]) * state->frameTime *
                   rotRate;
#endif

            if (state->moveOffset[i] < target[i]) {
#if EMULATE_X87
                if (x87f_lt(x87f_load_f32(step),
                            x87f_mul(x87f_load_f32(state->frameTime),
                                     x87f_load_f32(0.1f)))) {
                    step = state->frameTime * 0.1f;
                }
#else
                if (step < state->frameTime * 0.1f) {
                    step = state->frameTime * 0.1f;
                }
#endif
                state->moveOffset[i] += step;
                if (target[i] < state->moveOffset[i]) {
                    state->moveOffset[i] = target[i];
                }
            } else {
#if EMULATE_X87
                if (x87f_lt(x87f_mul(x87f_load_f32(state->frameTime),
                                     x87f_load_f32(-0.1f)),
                            x87f_load_f32(step))) {
                    step = state->frameTime * -0.1f;
                }
#else
                if (state->frameTime * -0.1f < step) {
                    step = state->frameTime * -0.1f;
                }
#endif
                state->moveOffset[i] += step;
                if (state->moveOffset[i] < target[i]) {
                    state->moveOffset[i] = target[i];
                }
            }
        }
    }

    if (client->ps.adsFraction != 0.0f) {
        float adsFraction = client->ps.adsFraction;

        if (adsFraction < 0.5f) {
            /* Stock 0x3814c: 1.0 - (ads+ads) one store; 0x38162: moveOffset[k]
             * *scale + angles[k], one store -> shim. */
#if EMULATE_X87
            float scale = x87f_store_f32(x87f_sub(
                x87f_load_f32(1.0f),
                x87f_add(x87f_load_f32(adsFraction), x87f_load_f32(adsFraction))));
            for (int k = 0; k < 3; k++) {
                angles[k] = x87f_store_f32(x87f_add(
                    x87f_mul(x87f_load_f32(state->moveOffset[k]),
                             x87f_load_f32(scale)),
                    x87f_load_f32(angles[k])));
            }
#else
            float scale = 1.0f - (adsFraction + adsFraction);
            angles[0] += state->moveOffset[0] * scale;
            angles[1] += state->moveOffset[1] * scale;
            angles[2] += state->moveOffset[2] * scale;
#endif
        }
    } else {
        angles[0] += state->moveOffset[0];
        angles[1] += state->moveOffset[1];
        angles[2] += state->moveOffset[2];
    }
}

/* VERIFIED_DECOMPILER(0x381e9, 481e9_FUN_000481e9.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; ADS-weapon pitch offset and base-position helper call checked against current decompiler output. */
void BG_CalculateWeaponPosition_BaseAngles(pm_weapon_angle_state_t *state,
                                           vec3_t angles)
{
    gclient_t *client = state->client;
    const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(client->ps.currentWeapon);

    if (BG_IsAimDownSightWeapon(client->ps.currentWeapon) != 0) {
        angles[0] +=
            client->ps.adsFraction *
            weaponInfo->adsPitchOffset;
    }

    BG_CalculateWeaponPosition_BasePosition_angles(state, angles);
}

/* VERIFIED_DECOMPILER(0x38264, 48264_FUN_00048264.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; ADS/hip idle amount, default 80.0, prone/crouch scale selection, idleScale lerp, and sin frequency/scale angle adds checked against current decompiler output. */
void BG_CalculateWeaponPosition_IdleAngles(pm_weapon_angle_state_t *state,
                                           vec3_t angles)
{
    gclient_t *client = state->client;
    const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(client->ps.currentWeapon);
    float idleAmount;
    float targetScale;

    if (BG_IsAimDownSightWeapon(client->ps.currentWeapon) != 0) {
        float hipAmount =
            weaponInfo->idleSwayHip;
        idleAmount =
            hipAmount +
            (weaponInfo->idleSwayAds -
             hipAmount) *
                client->ps.adsFraction;
    } else {
        idleAmount =
            weaponInfo->idleSwayHip;
        if (idleAmount == 0.0f) {
            idleAmount = WEAPON_IDLE_SWAY_DEFAULT;
        }
    }

    if ((client->ps.entityStateFlags & PS_FLAG_WEAPON_ROT_PRONE) != 0) {
        targetScale = weaponInfo->idleSwayProneScale;
    } else if ((client->ps.entityStateFlags & PS_FLAG_WEAPON_ROT_CROUCH) != 0) {
        targetScale = weaponInfo->idleSwayCrouchScale;
    } else {
        targetScale = 1.0f;
    }

    if (state->idleScale != targetScale) {
        if (state->idleScale < targetScale) {
            state->idleScale += state->frameTime * WEAPON_IDLE_SWAY_SCALE_STEP;
            if (targetScale < state->idleScale) {
                state->idleScale = targetScale;
            }
        } else {
            state->idleScale -= state->frameTime * WEAPON_IDLE_SWAY_SCALE_STEP;
            if (state->idleScale < targetScale) {
                state->idleScale = targetScale;
            }
        }
    }

    idleAmount *= state->idleScale;
    angles[2] += idleAmount *
                 (float)CoduoLibm_Sin((double)state->time *
                            WEAPON_IDLE_SWAY_ROLL_FREQ) *
                 WEAPON_IDLE_SWAY_ROLL_SCALE;
    angles[1] += idleAmount *
                 (float)CoduoLibm_Sin((double)state->time *
                            WEAPON_IDLE_SWAY_YAW_FREQ) *
                 WEAPON_IDLE_SWAY_YAW_SCALE;
    angles[0] += idleAmount *
                 (float)CoduoLibm_Sin((double)state->time *
                            WEAPON_IDLE_SWAY_PITCH_FREQ) *
                 WEAPON_IDLE_SWAY_PITCH_SCALE;
}

/* NOT_FROM_ORIGINAL_SOURCE: local bob helper factored from generated bob-position code. */
static float game_compat_bg_get_bob_amplitude(gclient_t *client, float speed)
{
    if (client->ps.viewHeightTarget == (int32_t)client->ps.proneViewHeight) {
        return speed * bg_bobAmplitudeProne.value;
    }
    if (client->ps.viewHeightTarget == (int32_t)client->ps.crouchViewHeight) {
        return speed * bg_bobAmplitudeDucked.value;
    }
    return speed * bg_bobAmplitudeStanding.value;
}

/* VERIFIED_DECOMPILER(0x37b2d, 47b2d_BG_GetBobCycle.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; bobCycle byte, /255*pi phase, doubled phase, and +2pi return checked against current decompiler output. */
float BG_GetBobCycle(gclient_t *client)
{
    /* Original i386 0x37b47..0x37b6a is one 80-bit chain with a single
     * float store at the end: fild byte / 255.0f * pi(float64), doubled,
     * + 2pi(float64), fstp DWORD. No intermediate phase rounding. */
#if EMULATE_X87
    /* fild byte / 255.0f(DWORD) * PI(QWORD double); phase+phase; +2PI(QWORD);
     * one float store (0x37b47..0x37b6a). */
    x87f phase = x87f_mul(
        x87f_div(x87f_load_i32((uint8_t)client->ps.bobCycle),
                 x87f_load_f32(WEAPON_BOB_CYCLE_MAX)),
        x87f_load_f64(WEAPON_BOB_PI));
    return x87f_store_f32(
        x87f_add(x87f_add(phase, phase), x87f_load_f64(WEAPON_BOB_TWO_PI)));
#else
    long double phase = (long double)(uint8_t)client->ps.bobCycle /
                        WEAPON_BOB_CYCLE_MAX *
                        WEAPON_BOB_PI;

    return (float)(phase + phase + WEAPON_BOB_TWO_PI);
#endif
}

/* VERIFIED_DECOMPILER(0x37b7c, 47b7c_BG_GetVerticalBobFactor.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; viewheight bob-amplitude cvar selection, max clamp, vertical sine/harmonic terms, and 0.75 scale checked against current decompiler output. */
float BG_GetVerticalBobFactor(gclient_t *client,
                              float phase,
                              float speed,
                              float maxAmplitude)
{
    float amplitude = game_compat_bg_get_bob_amplitude(client, speed);
    float base;
    float harmonic;

    if (maxAmplitude < amplitude) {
        amplitude = maxAmplitude;
    }

    base = (float)CoduoLibm_Sin((double)(phase + phase));
    harmonic =
        (float)CoduoLibm_Sin((double)(phase * WEAPON_BOB_VERTICAL_HARMONIC +
                            WEAPON_BOB_HALF_PI));
    /* Original i386 0x37c2c..0x37c53 rounds each stage to float: the
     * base+harmonic sum is stored, then 0.75f * base is stored, then the
     * amplitude product is stored before the return load. */
    base = base + harmonic * WEAPON_BOB_VERTICAL_HARMONIC_SCALE;
    base = WEAPON_BOB_VERTICAL_SCALE * base;
    base = base * amplitude;
    return base;
}

/* VERIFIED_DECOMPILER(0x37c5f, 47c5f_BG_GetHorizontalBobFactor.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; viewheight bob-amplitude cvar selection, max clamp, and horizontal sine return checked against current decompiler output. */
float BG_GetHorizontalBobFactor(gclient_t *client,
                                float phase,
                                float speed,
                                float maxAmplitude)
{
    float amplitude = game_compat_bg_get_bob_amplitude(client, speed);

    if (maxAmplitude < amplitude) {
        amplitude = maxAmplitude;
    }

    return (float)CoduoLibm_Sin((double)phase) * amplitude;
}

/* VERIFIED_DECOMPILER(0x38498, 48498_FUN_00048498.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; bob phase offsets, 0.16 speed scale, pitch/yaw negation, negative-only roll clamp, ADS bob factor scaling, and angle adds checked against current decompiler output. */
void BG_CalculateWeaponPosition_BobOffset(pm_weapon_angle_state_t *state, vec3_t angles)
{
    gclient_t *client = state->client;
    const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(client->ps.currentWeapon);
    float phase = BG_GetBobCycle(client);
    float speed = state->speed * WEAPON_BOB_SPEED_SCALE;
    float pitch;
    float yaw;
    float roll;

    /* Original i386 0x384f4..0x38507 adds quarter-pi and two-pi (both
     * float64) sequentially in x87; do not let the constants fold. */
    phase = phase + WEAPON_BOB_QUARTER_PI + WEAPON_BOB_TWO_PI;

    pitch = -BG_GetVerticalBobFactor(client, phase, speed,
                                     WEAPON_BOB_MAX_AMPLITUDE);
    yaw = -BG_GetHorizontalBobFactor(client, phase, speed,
                                    WEAPON_BOB_MAX_AMPLITUDE);
    roll = BG_GetHorizontalBobFactor(
        client, phase - WEAPON_BOB_ROLL_PHASE,
        speed * WEAPON_BOB_ROLL_SPEED_SCALE, WEAPON_BOB_MAX_AMPLITUDE);
    if (roll < 0.0f) {
        roll = BG_GetHorizontalBobFactor(
            client, phase - WEAPON_BOB_ROLL_PHASE,
            speed * WEAPON_BOB_ROLL_SPEED_SCALE,
            WEAPON_BOB_MAX_AMPLITUDE);
    } else {
        roll = 0.0f;
    }

    if (client->ps.adsFraction != 0.0f) {
        float adsFraction =
            client->ps.adsFraction;
        float bobScale =
            1.0f -
            (1.0f - weaponInfo->adsBobFactor) *
                adsFraction;

        pitch *= bobScale;
        yaw *= bobScale;
        roll *= bobScale;
    }

    angles[0] += pitch;
    angles[1] += yaw;
    angles[2] += roll;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from generated damage-kick code. */
static void game_compat_bg_add_view_kick_angles(pm_weapon_angle_state_t *state,
                                 vec3_t angles,
                                 float fraction)
{
    /* (fraction*kick)*scale +/- angles[k], each kept 80-bit, one store -> shim. */
#if EMULATE_X87
    angles[0] = x87f_store_f32(x87f_add(
        x87f_mul(x87f_mul(x87f_load_f32(fraction),
                          x87f_load_f32(state->viewKickPitch)),
                 x87f_load_f32(WEAPON_VIEWKICK_PITCH_SCALE)),
        x87f_load_f32(angles[0])));
    angles[1] = x87f_store_f32(x87f_sub(
        x87f_load_f32(angles[1]),
        x87f_mul(x87f_load_f32(fraction), x87f_load_f32(state->viewKickYaw))));
    angles[2] = x87f_store_f32(x87f_add(
        x87f_mul(x87f_mul(x87f_load_f32(fraction),
                          x87f_load_f32(state->viewKickYaw)),
                 x87f_load_f32(WEAPON_VIEWKICK_ROLL_SCALE)),
        x87f_load_f32(angles[2])));
#else
    angles[0] += fraction * state->viewKickPitch *
                 WEAPON_VIEWKICK_PITCH_SCALE;
    angles[1] -= fraction * state->viewKickYaw;
    angles[2] += fraction * state->viewKickYaw *
                 WEAPON_VIEWKICK_ROLL_SCALE;
#endif
}

/* VERIFIED_DECOMPILER(0x38687, 48687_FUN_00048687.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; damage-kick start guard, ADS scale/reduction gate, 100/400 ms fade, and pitch/yaw/roll helper adds checked against current decompiler output. */
void BG_CalculateWeaponPosition_DamageKick(pm_weapon_angle_state_t *state, vec3_t angles)
{
    if (state->viewKickStartTime == 0) {
        return;
    }

    gclient_t *client = state->client;
    const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(client->ps.currentWeapon);
    float adsFraction = client->ps.adsFraction;
    /* ads*BASE + BASE kept 80-bit, one store -> shim; fadeInTime/fadeOutTime are
     * single muls (native). */
#if EMULATE_X87
    float scale = x87f_store_f32(x87f_add(
        x87f_mul(x87f_load_f32(adsFraction),
                 x87f_load_f32(WEAPON_VIEWKICK_ADS_BASE)),
        x87f_load_f32(WEAPON_VIEWKICK_ADS_BASE)));
#else
    float scale = adsFraction * WEAPON_VIEWKICK_ADS_BASE +
                  WEAPON_VIEWKICK_ADS_BASE;
#endif
    float fadeInTime = scale * WEAPON_VIEWKICK_FADE_IN_MS;
    float fadeOutTime = scale * WEAPON_VIEWKICK_FADE_OUT_MS;
    float elapsed;

    if (adsFraction != 0.0f &&
        weaponInfo->overlayReticleAdsReduceGate != 0) {
        /* scale * (1 - ads*REDUCTION) kept 80-bit, one store -> shim. */
#if EMULATE_X87
        scale = x87f_store_f32(x87f_mul(
            x87f_load_f32(scale),
            x87f_sub(x87f_load_f32(1.0f),
                     x87f_mul(x87f_load_f32(adsFraction),
                              x87f_load_f32(WEAPON_VIEWKICK_ADS_REDUCTION)))));
#else
        scale *= 1.0f - adsFraction * WEAPON_VIEWKICK_ADS_REDUCTION;
#endif
    }

    elapsed = (float)(state->time - state->viewKickStartTime);
    if (elapsed < fadeInTime) {
        /* GetLeanFraction(elapsed/fadeInTime) * scale kept 80-bit (the divide and
         * the lean chain via game_compat_get_lean_fraction_x87), rounded once at the arg. */
#if EMULATE_X87
        float leanArg = x87f_store_f32(
            x87f_div(x87f_load_f32(elapsed), x87f_load_f32(fadeInTime)));
        game_compat_bg_add_view_kick_angles(state, angles, x87f_store_f32(
            x87f_mul(game_compat_get_lean_fraction_x87(leanArg), x87f_load_f32(scale))));
#else
        game_compat_bg_add_view_kick_angles(state, angles,
                             GetLeanFraction(elapsed / fadeInTime) * scale);
#endif
    } else {
        /* 1 - (elapsed-fadeInTime)/fadeOutTime kept 80-bit, one store -> shim. */
#if EMULATE_X87
        float fadeOutFraction = x87f_store_f32(x87f_sub(
            x87f_load_f32(1.0f),
            x87f_div(x87f_sub(x87f_load_f32(elapsed), x87f_load_f32(fadeInTime)),
                     x87f_load_f32(fadeOutTime))));
#else
        float fadeOutFraction = 1.0f - (elapsed - fadeInTime) / fadeOutTime;
#endif

        if (fadeOutFraction > 0.0f) {
            /* Original i386 0x3882b..0x38833 rounds 1.0 - GetLeanFraction(..)
             * to float before the scale multiply; fadeOutLean*scale = single mul. */
#if EMULATE_X87
            float leanArg = x87f_store_f32(
                x87f_sub(x87f_load_f32(1.0f), x87f_load_f32(fadeOutFraction)));
            float fadeOutLean = x87f_store_f32(
                x87f_sub(x87f_load_f32(1.0f), game_compat_get_lean_fraction_x87(leanArg)));
#else
            float fadeOutLean =
                1.0f - GetLeanFraction(1.0f - fadeOutFraction);
#endif

            game_compat_bg_add_view_kick_angles(state, angles, fadeOutLean * scale);
        }
    }
}

/* VERIFIED_DECOMPILER(0x388a0, 488a0_FUN_000488a0.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; recoil epsilon zero return, angle/max clamp, return acceleration, damping, friction, max velocity clamp, and return flag checked against current decompiler output. */
qboolean BG_CalculateWeaponPosition_GunRecoil_SingleAngle(float *angle,
                                   float *velocity,
                                   float frameTime,
                                   float maxAngle,
                                   float returnAcceleration,
                                   float maxVelocity,
                                   float damping,
                                   float friction)
{
    if (fabsf(*angle) < WEAPON_RECOIL_ANGLE_EPSILON &&
        fabsf(*velocity) < WEAPON_RECOIL_VELOCITY_EPSILON) {
        *angle = 0.0f;
        *velocity = 0.0f;
        return 1;
    }

    /* Stock 0x3890c..0x38a25: each recoil integration step keeps its product
     * 80-bit and does one store; the -= forms are `v - prod` (fsubrp) -> shim. */
#if EMULATE_X87
    *angle = x87f_store_f32(x87f_add(
        x87f_mul(x87f_load_f32(*velocity), x87f_load_f32(frameTime)),
        x87f_load_f32(*angle)));
#else
    *angle += *velocity * frameTime;
#endif
    if (*angle > maxAngle) {
        *angle = maxAngle;
        if (*velocity > 0.0f) {
            *velocity = 0.0f;
        }
    } else if (*angle < -maxAngle) {
        *angle = -maxAngle;
        if (*velocity < 0.0f) {
            *velocity = 0.0f;
        }
    }

    if (*angle > 0.0f) {
#if EMULATE_X87
        *velocity = x87f_store_f32(x87f_sub(x87f_load_f32(*velocity),
            x87f_mul(x87f_load_f32(returnAcceleration), x87f_load_f32(frameTime))));
#else
        *velocity -= returnAcceleration * frameTime;
#endif
    } else if (*angle < 0.0f) {
#if EMULATE_X87
        *velocity = x87f_store_f32(x87f_add(
            x87f_mul(x87f_load_f32(returnAcceleration), x87f_load_f32(frameTime)),
            x87f_load_f32(*velocity)));
#else
        *velocity += returnAcceleration * frameTime;
#endif
    }

#if EMULATE_X87
    *velocity = x87f_store_f32(x87f_sub(x87f_load_f32(*velocity),
        x87f_mul(x87f_mul(x87f_load_f32(*velocity), x87f_load_f32(damping)),
                 x87f_load_f32(frameTime))));
#else
    *velocity -= *velocity * damping * frameTime;
#endif
    if (*velocity > 0.0f) {
#if EMULATE_X87
        *velocity = x87f_store_f32(x87f_sub(x87f_load_f32(*velocity),
            x87f_mul(x87f_load_f32(friction), x87f_load_f32(frameTime))));
#else
        *velocity -= friction * frameTime;
#endif
        if (*velocity < 0.0f) {
            *velocity = 0.0f;
        }
    } else {
#if EMULATE_X87
        *velocity = x87f_store_f32(x87f_add(
            x87f_mul(x87f_load_f32(friction), x87f_load_f32(frameTime)),
            x87f_load_f32(*velocity)));
#else
        *velocity += friction * frameTime;
#endif
        if (*velocity > 0.0f) {
            *velocity = 0.0f;
        }
    }

    if (*velocity > maxVelocity) {
        *velocity = maxVelocity;
    } else if (*velocity < -maxVelocity) {
        *velocity = -maxVelocity;
    }

    return 0;
}

/* NOT_FROM_ORIGINAL_SOURCE: local interpolation helper factored from generated sway/recoil code. */
static float game_compat_lerp_by_ads(float hipValue, float adsValue, float adsFraction)
{
    return hipValue + (adsValue - hipValue) * adsFraction;
}

/* NOT_FROM_ORIGINAL_SOURCE: local random-range helper factored from generated sway/recoil code. */
static float game_compat_random_float_range(float minValue, float maxValue)
{
    float delta = maxValue - minValue;

    /* minValue + rand*delta kept 80-bit, one float store -> shim. */
#if EMULATE_X87
    return x87f_store_f32(x87f_add(
        x87f_load_f32(minValue),
        x87f_mul(game_compat_item_random_half_open_extended_x87(), x87f_load_f32(delta))));
#else
    return (float)((long double)minValue +
                   game_compat_item_random_half_open_extended() * (long double)delta);
#endif
}

/* NOT_FROM_ORIGINAL_SOURCE: local clamp helper factored from generated sway code. */
static float game_compat_bg_clamp_sway_delta(float value, float maxAngle)
{
    if (value < -maxAngle) {
        return -maxAngle;
    }
    if (value > maxAngle) {
        return maxAngle;
    }
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: local wrap helper factored from generated sway code. */
static float game_compat_bg_wrap_sway_target_for_smooth(float target, float current)
{
    while (target - current > 180.0f) {
        target -= 360.0f;
    }
    return target;
}

/* NOT_FROM_ORIGINAL_SOURCE: local parameter-load helper factored from generated sway code. */
static void game_compat_bg_load_weapon_sway_params(const weaponInfo_t *weaponInfo,
                                    qboolean isAdsWeapon,
                                    float adsFraction,
                                    bg_weapon_sway_params_t *params)
{
    if (isAdsWeapon == 0) {
        params->maxAngle = weaponInfo->swayMaxAngle;
        params->smoothRate = weaponInfo->swayLerpSpeed;
        params->pitchAngleScale = weaponInfo->swayPitchScale;
        params->yawAngleScale = weaponInfo->swayYawScale;
        params->yawOffsetScale = weaponInfo->swayHorizScale;
        params->pitchOffsetScale = weaponInfo->swayVertScale;
        return;
    }

    params->maxAngle = game_compat_lerp_by_ads(weaponInfo->swayMaxAngle,
                                 weaponInfo->adsSwayMaxAngle, adsFraction);
    params->smoothRate = game_compat_lerp_by_ads(weaponInfo->swayLerpSpeed,
                                   weaponInfo->adsSwayLerpSpeed, adsFraction);
    params->pitchAngleScale = game_compat_lerp_by_ads(weaponInfo->swayPitchScale,
                                        weaponInfo->adsSwayPitchScale, adsFraction);
    params->yawAngleScale = game_compat_lerp_by_ads(weaponInfo->swayYawScale,
                                      weaponInfo->adsSwayYawScale, adsFraction);
    params->yawOffsetScale = game_compat_lerp_by_ads(weaponInfo->swayHorizScale,
                                       weaponInfo->adsSwayHorizScale, adsFraction);
    params->pitchOffsetScale = game_compat_lerp_by_ads(weaponInfo->swayVertScale,
                                         weaponInfo->adsSwayVertScale, adsFraction);
}

/* VERIFIED_DECOMPILER(0x38ac6, 48ac6_FUN_00048ac6.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; vehicle/ADS gates, ADS lerps for recoil envelope, 0.005 step loop, pitch/yaw recoil helper calls, early break, and angle adds checked against current decompiler output. */
void BG_CalculateWeaponPosition_GunRecoil(pm_weapon_angle_state_t *state, vec3_t angles)
{
    gclient_t *client = state->client;

    if ((client->ps.entityStateFlags & PS_FLAG_IN_VEHICLE) != 0 &&
        BG_AllowPlayerWeaponAtVehiclePos(
            client->ps.vehicleType,
            client->ps.vehiclePosition) == 0) {
        return;
    }
    if (BG_IsAimDownSightWeapon(client->ps.currentWeapon) == 0) {
        return;
    }

    const weaponInfo_t *wi = (const weaponInfo_t *)BG_GetInfoForWeapon(client->ps.currentWeapon);
    float adsFraction = client->ps.adsFraction;
    float returnAcceleration = game_compat_lerp_by_ads(wi->recoilReturnHip,
                                         wi->recoilReturnAds, adsFraction);
    float maxVelocity = game_compat_lerp_by_ads(wi->recoilVelocityHip,
                                  wi->recoilVelocityAds, adsFraction);
    float damping = game_compat_lerp_by_ads(wi->recoilDampingHip,
                              wi->recoilDampingAds, adsFraction);
    float friction = game_compat_lerp_by_ads(wi->recoilFrictionHip,
                               wi->recoilFrictionAds, adsFraction);
    float remaining = state->frameTime;

    while (remaining > 0.0f) {
        float step;
        qboolean pitchDone;
        qboolean yawDone;

        if (remaining > WEAPON_RECOIL_STEP_TIME_F64) {
            step = WEAPON_RECOIL_STEP_TIME;
            remaining -= WEAPON_RECOIL_STEP_TIME_F64;
        } else {
            step = remaining;
            remaining = 0.0f;
        }

        pitchDone = BG_CalculateWeaponPosition_GunRecoil_SingleAngle(
            &state->recoilPitch, &state->recoilPitchVelocity, step,
            wi->gunMaxPitch,
            returnAcceleration, maxVelocity, damping, friction);
        yawDone = BG_CalculateWeaponPosition_GunRecoil_SingleAngle(
            &state->recoilYaw, &state->recoilYawVelocity, step,
            wi->gunMaxYaw,
            returnAcceleration, maxVelocity, damping, friction);
        if (pitchDone != 0 && yawDone != 0) {
            break;
        }
    }

    angles[0] += state->recoilPitch;
    angles[1] += state->recoilYaw;
    angles[2] += state->recoilRoll;
}

/* NOT_FROM_ORIGINAL_SOURCE: local helper factored from generated view-kick code. */
static void game_compat_bg_add_view_kick_pitch_roll(bg_view_angle_state_t *state,
                                    vec3_t angles,
                                    float fraction)
{
    /* fraction*kick + angles[k], kept 80-bit, one store -> shim. */
#if EMULATE_X87
    angles[0] = x87f_store_f32(x87f_add(
        x87f_mul(x87f_load_f32(fraction), x87f_load_f32(state->viewKickPitch)),
        x87f_load_f32(angles[0])));
    angles[2] = x87f_store_f32(x87f_add(
        x87f_mul(x87f_load_f32(fraction), x87f_load_f32(state->viewKickRoll)),
        x87f_load_f32(angles[2])));
#else
    angles[0] += fraction * state->viewKickPitch;
    angles[2] += fraction * state->viewKickRoll;
#endif
}

/* VERIFIED_DECOMPILER(0x38e76, 48e76_FUN_00048e76.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; view-kick start guard, 0x106000 vehicle/fire-position gate, ADS scale/reduction gate, 100/400 ms fade, and pitch/roll adds checked against current decompiler output. */
void BG_CalculateView_DamageKick(bg_view_angle_state_t *state, vec3_t angles)
{
    if (state->viewKickStartTime == 0) {
        return;
    }

    gclient_t *client = state->client;
    if ((client->ps.entityStateFlags & PS_FLAG_FIRE_POSITION_CHECK_MASK) != 0 &&
        BG_AllowPlayerWeaponAtVehiclePos(
            client->ps.vehicleType,
            client->ps.vehiclePosition) == 0) {
        return;
    }

    const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(client->ps.currentWeapon);
    float adsFraction = client->ps.adsFraction;
    /* 1 - ads*SCALE kept 80-bit, one store -> shim. */
#if EMULATE_X87
    float scale = x87f_store_f32(x87f_sub(
        x87f_load_f32(1.0f),
        x87f_mul(x87f_load_f32(adsFraction), x87f_load_f32(VIEWKICK_ADS_SCALE))));
#else
    float scale = 1.0f - adsFraction * VIEWKICK_ADS_SCALE;
#endif
    float elapsed;

    if (adsFraction != 0.0f &&
        weaponInfo->overlayReticleAdsReduceGate != 0) {
        /* scale * (ads*SCALE + 1) kept 80-bit, one store -> shim. */
#if EMULATE_X87
        scale = x87f_store_f32(x87f_mul(
            x87f_load_f32(scale),
            x87f_add(x87f_mul(x87f_load_f32(adsFraction),
                              x87f_load_f32(VIEWKICK_ADS_SCALE)),
                     x87f_load_f32(1.0f))));
#else
        scale *= adsFraction * VIEWKICK_ADS_SCALE + 1.0f;
#endif
    }

    elapsed = (float)(state->time - state->viewKickStartTime);
    if (elapsed < VIEWKICK_FADE_IN_MS) {
#if EMULATE_X87
        float leanArg = x87f_store_f32(x87f_div(
            x87f_load_f32(elapsed), x87f_load_f32(VIEWKICK_FADE_IN_MS)));
        game_compat_bg_add_view_kick_pitch_roll(state, angles, x87f_store_f32(
            x87f_mul(game_compat_get_lean_fraction_x87(leanArg), x87f_load_f32(scale))));
#else
        game_compat_bg_add_view_kick_pitch_roll(
            state, angles,
            GetLeanFraction(elapsed / VIEWKICK_FADE_IN_MS) * scale);
#endif
    } else {
#if EMULATE_X87
        float fadeOutFraction = x87f_store_f32(x87f_sub(
            x87f_load_f32(1.0f),
            x87f_div(
                x87f_sub(x87f_load_f32(elapsed),
                         x87f_load_f32(VIEWKICK_FADE_IN_MS)),
                x87f_load_f32(VIEWKICK_FADE_OUT_MS))));
#else
        float fadeOutFraction =
            1.0f - (elapsed - VIEWKICK_FADE_IN_MS) /
                       VIEWKICK_FADE_OUT_MS;
#endif

        if (fadeOutFraction > 0.0f) {
            /* Original i386 0x39018..0x39020 rounds 1.0 - GetLeanFraction(..)
             * to float before the scale multiply. */
#if EMULATE_X87
            float leanArg = x87f_store_f32(
                x87f_sub(x87f_load_f32(1.0f), x87f_load_f32(fadeOutFraction)));
            float fadeOutLean = x87f_store_f32(
                x87f_sub(x87f_load_f32(1.0f), game_compat_get_lean_fraction_x87(leanArg)));
#else
            float fadeOutLean =
                1.0f - GetLeanFraction(1.0f - fadeOutFraction);
#endif

            game_compat_bg_add_view_kick_pitch_roll(state, angles, fadeOutLean * scale);
        }
    }
}

/* VERIFIED_DECOMPILER(0x39062, 49062_FUN_00049062.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; weapon-info load, 0x106000 vehicle/fire-position gate, ADS/view-bob nonzero gates, bob factors, and pitch/yaw subtraction checked against current decompiler output. */
void BG_CalculateView_Velocity(bg_view_angle_state_t *state, vec3_t angles)
{
    gclient_t *client = state->client;
    const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(client->ps.currentWeapon);
    float adsFraction;
    float bobScale;
    float phase;

    if ((client->ps.entityStateFlags & PS_FLAG_FIRE_POSITION_CHECK_MASK) != 0 &&
        BG_AllowPlayerWeaponAtVehiclePos(
            client->ps.vehicleType,
            client->ps.vehiclePosition) == 0) {
        return;
    }

    adsFraction = client->ps.adsFraction;
    bobScale = weaponInfo->adsViewBobScale;
    if (adsFraction == 0.0f || bobScale == 0.0f) {
        return;
    }

    phase = BG_GetBobCycle(client);

    /* Original i386 0x39159..0x391d5 stores each bob factor to a float
     * temp, scales it in place ((ads * bobScale) * bob, one float store),
     * and only then subtracts it from the angle. */
    {
        float bob;

        bob = BG_GetVerticalBobFactor(client, phase, state->speed,
                                      VIEW_BOB_MAX_AMPLITUDE);
        bob = adsFraction * bobScale * bob;
        angles[0] -= bob;

        bob = BG_GetHorizontalBobFactor(client, phase, state->speed,
                                        VIEW_BOB_MAX_AMPLITUDE);
        bob = adsFraction * bobScale * bob;
        angles[1] -= bob;
    }
}

/* VERIFIED_DECOMPILER(0x391dd, 491dd_BG_CalculateViewAngles.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; zero initialization and damage-kick/velocity helper order checked against current decompiler output. */
void BG_CalculateViewAngles(bg_view_angle_state_t *state, float *viewAngles)
{
    viewAngles[0] = 0.0f;
    viewAngles[1] = 0.0f;
    viewAngles[2] = 0.0f;
    BG_CalculateView_DamageKick(state, viewAngles);
    BG_CalculateView_Velocity(state, viewAngles);
}

/* MACHINE_CODE_AUDITED(0x38d25,
 * SERVER-CROSS-PLATFORM-C-2026-07-25): DATAFLOW_VERIFIED; stock 0x38d94 loads
 * the float directly from client + 0x44 for the unordered/nonzero comparison,
 * and 0x38dad passes that same dword to GetLeanFraction. */
void BG_CalculateWeaponAngles(pm_weapon_angle_state_t *state,
                              float *weaponAngles)
{
    gclient_t *client = state->client;
    float leanFraction = client->ps.leanFraction;

    if ((client->ps.entityStateFlags & PS_FLAG_IN_VEHICLE) != 0 &&
        BG_AllowPlayerWeaponAtVehiclePos(client->ps.vehicleType,
                                         client->ps.vehiclePosition) == 0) {
        return;
    }

    weaponAngles[0] = 0.0f;
    weaponAngles[1] = 0.0f;
    weaponAngles[2] = 0.0f;

    if (leanFraction != 0.0f || isnan(leanFraction)) {
        /* weaponAngles[2] - GetLeanFraction(lean)*2.0 kept 80-bit, one store. */
#if EMULATE_X87
        weaponAngles[2] = x87f_store_f32(x87f_sub(
            x87f_load_f32(weaponAngles[2]),
            x87f_mul(game_compat_get_lean_fraction_x87(leanFraction),
                     x87f_load_f32(2.0f))));
#else
        weaponAngles[2] -= GetLeanFraction(leanFraction) * 2.0f;
#endif
    }

    BG_CalculateWeaponPosition_BaseAngles(state, weaponAngles);
    BG_CalculateWeaponPosition_IdleAngles(state, weaponAngles);
    BG_CalculateWeaponPosition_BobOffset(state, weaponAngles);
    BG_CalculateWeaponPosition_DamageKick(state, weaponAngles);
    BG_CalculateWeaponPosition_GunRecoil(state, weaponAngles);

    weaponAngles[0] = AngleSubtract(weaponAngles[0], state->baseAngles[0]);
    weaponAngles[1] = AngleSubtract(weaponAngles[1], state->baseAngles[1]);
}

/* VERIFIED_DECOMPILER(0x39236, 49236_FUN_00049236.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; delta step math, 0.001 epsilon snap, overshoot snap, and current+step return checked against current decompiler output. */
float BG_SmoothWeaponSwayValue(float target,
                               float current,
                               float rate,
                               int msec)
{
    float delta = target - current;
    /* Original i386 0x39251..0x39264 groups the product as
     * (rate * delta) * (msec * 0.001f) with msec fild'd raw -> shim. */
#if EMULATE_X87
    float step = x87f_store_f32(x87f_mul(
        x87f_mul(x87f_load_f32(rate), x87f_load_f32(delta)),
        x87f_mul(x87f_load_i32(msec),
                 x87f_load_f32(WEAPON_SWAY_MSEC_TO_SEC))));
#else
    float step = rate * delta *
                 ((long double)msec * WEAPON_SWAY_MSEC_TO_SEC);
#endif

    if (!(fabsf(delta) > WEAPON_SWAY_SMOOTH_EPSILON)) {
        return target;
    }
    if (fabsf(delta) < fabsf(step)) {
        return target;
    }

    return current + step;
}

/* VERIFIED_DECOMPILER(0x392b2, 492b2_BG_CalculateWeaponPosition_Sway.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; ADS overlay gate, repaired non-ADS hip-only parameter selection, scaled sway params, view delta clamps, offset/angle smoothing, wrap/normalize, and previous-view stores checked against current decompiler output. */
void BG_CalculateWeaponPosition_Sway(gclient_t *client,
                                     float *previousViewAngles,
                                     float *swayOffsets,
                                     float *swayAngles,
                                     float scale,
                                     int msec)
{
    const weaponInfo_t *weaponInfo = BG_GetInfoForWeapon(client->ps.currentWeapon);
    float adsFraction = client->ps.adsFraction;
    qboolean isAdsWeapon = BG_IsAimDownSightWeapon(client->ps.currentWeapon);
    bg_weapon_sway_params_t params;
    vec3_t viewDelta;
    float pitchDelta;
    float yawDelta;
    float targetPitchAngle;
    float targetYawAngle;

    if (isAdsWeapon != 0) {
        if (adsFraction > 0.0f &&
            weaponInfo->overlayReticleAdsReduceGate != 0) {
            return;
        }
    } else {
        adsFraction = 0.0f;
    }

    game_compat_bg_load_weapon_sway_params(weaponInfo, isAdsWeapon,
                                           adsFraction, &params);
    params.pitchAngleScale *= scale;
    params.yawAngleScale *= scale;
    params.yawOffsetScale *= scale;
    params.pitchOffsetScale *= scale;

    AnglesSubtract(client->ps.viewAngles, previousViewAngles, viewDelta);
    pitchDelta = game_compat_bg_clamp_sway_delta(viewDelta[0], params.maxAngle);
    yawDelta = game_compat_bg_clamp_sway_delta(viewDelta[1], params.maxAngle);

    swayOffsets[1] = BG_SmoothWeaponSwayValue(
        yawDelta * params.yawOffsetScale, swayOffsets[1],
        params.smoothRate, msec);
    swayOffsets[2] = BG_SmoothWeaponSwayValue(
        pitchDelta * params.pitchOffsetScale, swayOffsets[2],
        params.smoothRate, msec);

    targetPitchAngle = game_compat_bg_wrap_sway_target_for_smooth(
        pitchDelta * params.pitchAngleScale, swayAngles[0]);
    targetYawAngle = game_compat_bg_wrap_sway_target_for_smooth(
        yawDelta * params.yawAngleScale, swayAngles[1]);

    swayAngles[0] = BG_SmoothWeaponSwayValue(
        targetPitchAngle, swayAngles[0], params.smoothRate, msec);
    swayAngles[1] = BG_SmoothWeaponSwayValue(
        targetYawAngle, swayAngles[1], params.smoothRate, msec);
    swayAngles[0] = (float)AngleNormalize180(swayAngles[0]);
    swayAngles[1] = (float)AngleNormalize180(swayAngles[1]);

    previousViewAngles[0] = client->ps.viewAngles[0];
    previousViewAngles[1] = client->ps.viewAngles[1];
    previousViewAngles[2] = client->ps.viewAngles[2];
}

/* VERIFIED_DECOMPILER(0x396bc, 496bc_BG_WeaponFireRecoil.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; ADS==1 viewkick ranges, ADS>0 recoil ranges, rand denominator, -0.5 viewkick roll, and recoil velocity adds checked against current decompiler output. */
void BG_WeaponFireRecoil(gclient_t *client,
                         float recoilVelocity[2],
                         vec3_t viewKick)
{
    const weaponInfo_t *wi = (const weaponInfo_t *)BG_GetInfoForWeapon(client->ps.currentWeapon);
    float adsFraction = client->ps.adsFraction;
    float viewKickPitch;
    float viewKickYaw;
    float recoilPitch;
    float recoilYaw;

    if (adsFraction == 1.0f) {
        viewKickPitch = game_compat_random_float_range(wi->fireViewkickAdsPitchMin,
                                         wi->fireViewkickAdsPitchMax);
        viewKickYaw = game_compat_random_float_range(wi->fireViewkickAdsYawMin,
                                       wi->fireViewkickAdsYawMax);
    } else {
        viewKickPitch = game_compat_random_float_range(wi->fireViewkickHipPitchMin,
                                         wi->fireViewkickHipPitchMax);
        viewKickYaw = game_compat_random_float_range(wi->fireViewkickHipYawMin,
                                       wi->fireViewkickHipYawMax);
    }

    viewKick[0] = -viewKickPitch;
    viewKick[1] = viewKickYaw;
    viewKick[2] = viewKickYaw * WEAPON_FIRE_RECOIL_VIEWKICK_ROLL_SCALE;

    if (adsFraction > 0.0f) {
        recoilPitch = game_compat_random_float_range(wi->fireRecoilAdsPitchMin,
                                       wi->fireRecoilAdsPitchMax);
        recoilYaw = game_compat_random_float_range(wi->fireRecoilAdsYawMin,
                                     wi->fireRecoilAdsYawMax);
    } else {
        recoilPitch = game_compat_random_float_range(wi->fireRecoilHipPitchMin,
                                       wi->fireRecoilHipPitchMax);
        recoilYaw = game_compat_random_float_range(wi->fireRecoilHipYawMin,
                                     wi->fireRecoilHipYawMax);
    }

    recoilVelocity[0] += recoilPitch;
    recoilVelocity[1] += recoilYaw;
}

/* VERIFIED_DECOMPILER(0x33bad, 43bad_BG_ClipForWeapon.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; BG_GetInfoForWeapon call and weaponInfo_t +0x1f0 clipIndex return checked against current decompiler output. */
int BG_ClipForWeapon(int weapon)
{
    const weaponInfo_t *wi = (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);

    return wi->clipIndex;
}

/* VERIFIED_DECOMPILER(0x33bd6, 43bd6_BG_AmmoForWeapon.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; BG_GetInfoForWeapon call and weaponInfo_t +0x1e8 ammoIndex return checked against current decompiler output. */
int BG_AmmoForWeapon(int weapon)
{
    const weaponInfo_t *wi = (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);

    return wi->ammoIndex;
}

/* VERIFIED_DECOMPILER(0x33bff, 43bff_BG_WeaponIsClipOnly.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; BG_GetInfoForWeapon call and weaponInfo_t +0x340 clipRequired return checked against current decompiler output. */
int BG_WeaponIsClipOnly(int weapon)
{
    const weaponInfo_t *wi = (const weaponInfo_t *)BG_GetInfoForWeapon(weapon);

    return wi->clipRequired;
}

/* VERIFIED_DECOMPILER(0x33e90, 43e90_PM_WeaponUseAmmo.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; clip index lookup, clip decrement, and zero clamp checked against current decompiler output. */
void PM_WeaponUseAmmo(int weapon, int amount)
{
    playerState_t *client = pm->ps;
    int clipIndex = BG_ClipForWeapon(weapon);

    client->clips[clipIndex] = coduo_int32_from_bits(
        (uint32_t)client->clips[clipIndex] - (uint32_t)amount);
    if (client->clips[clipIndex] < 0) {
        client->clips[clipIndex] = 0;
    }
}

/* VERIFIED_DECOMPILER(0x33f16, 43f16_PM_WeaponAmmoAvailable.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; clip index lookup and client clips array return checked against current decompiler output. */
int PM_WeaponAmmoAvailable(int weapon)
{
    return pm->ps->clips[BG_ClipForWeapon(weapon)];
}

/* VERIFIED_DECOMPILER(0x33f50, 43f50_PM_WeaponClipEmpty.c, VERIFY-ITEMS-WEAPON-VIEW-2026-06-17): DATAFLOW_VERIFIED; clip index lookup, client clips array read, and zero comparison return checked against current decompiler output. */
qboolean PM_WeaponClipEmpty(int weapon)
{
    return pm->ps->clips[BG_ClipForWeapon(weapon)] == 0;
}
