/*
 * Source reconstruction for small game-module helpers.
 *
 * Binary SHA-256:
 * 4d2391c10e234559ace294fb69d96741cad522653c361f13f626f4dc1891acb7
 */

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

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

#define GAME_PRINT_BUFFER_SIZE 1024
#define EVENT_RING_MASK 3
#define ACTIVATE_MAX_CANDIDATES 1024
#define ACTIVATE_SEARCH_XY 192.0f
#define ACTIVATE_SEARCH_Z 96.0f
#define ACTIVATE_MAX_DISTANCE 128.0f
#define ACTIVATE_MIN_DOT 0.76f
#define ACTIVATE_UNUSABLE_ITEM_PENALTY 10000.0f
#define ACTIVATE_OCCLUDED_PENALTY 100000.0f
#define ACTIVATE_ENTITY_CONTENTS 0x00200000u
#define ACTIVATE_VEHICLE_TRACE_MASK 0x02810011u
#define ACTIVATE_VIS_TRACE_MASK 0x00800011u
#define CURSOR_MOUNT_CONTENTS 0x00404000u
#define CURSOR_FLAG_WORLD_HINT 0x00000008u
#define CURSOR_PLAYERSTATE_WORLD_HINT_BLOCKED 0x00000010u
#define CURSOR_PS_FLAG_IN_VEHICLE 0x00100000u
#define CURSOR_STANCE_FLAG 0x00000020u
#define CURSOR_HINT_ITEM_BASE 10
#define CURSOR_HINT_OWNED_WEAPON_BASE 138
#define CURSOR_HINT_STRING_INHERIT 255
#define PREVENT_FRIENDLY_FIRE_MASK 0x20000001u
#define PREVENT_FRIENDLY_FIRE_CONFIRM_MASK 0x22802001u
#define PLAYER_RECOIL_VIEWKICK_EVENT_MIN EV_FIRE_WEAPON
#define PLAYER_RECOIL_VIEWKICK_EVENT_MAX EV_FIRE_WEAPON_LASTSHOT
#define COM_MAX_PARSE_SESSIONS 16
#define COM_PARSE_SESSION_OVERFLOW \
    COM_ERROR_MARKER "Com_BeginParseSession: session overflow"
#define COM_PARSE_SESSION_UNDERFLOW \
    COM_ERROR_MARKER "Com_EndParseSession: session underflow"
#define COM_PARSE_MESSAGE_BUFFER_SIZE 32000
#define COM_PARSE_ERROR_FORMAT COM_ERROR_MARKER "File %s, line %i: %s"
#define COM_PARSE_WARNING_FORMAT "File %s, line %i: %s"
#define COM_PARSE_TOKEN_SIZE 1024
#define COM_PARSE_NULL_DATA_ERROR \
    COM_ERROR_MARKER "Com_ParseExt: NULL data_p"
#define COM_PARSE_UNGET_TWICE_ERROR "UngetToken called twice"
#define COM_MATCH_TOKEN_ERROR_FORMAT "MatchToken: %s != %s"
#define Q_WHOLE_STRING 99999
#define VA_TEMP_BUFFER_SIZE 32000
#define VA_MAX_RESULT_LENGTH 31999
#define VA_WRAP_LIMIT 31998
#define VA_OVERRUN_ERROR \
    COM_ERROR_MARKER "Attempted to overrun string in call to va()\n"
#define Q_STRCAT_OVERFLOW_ERROR COM_ERROR_MARKER "Q_strcat: already overflowed"
#define CP1252_RIGHT_SINGLE_QUOTE 146u

void *trap_Hunk_AllocAlignInternal(size_t size, int alignment);
qboolean G_IsVehicleUsable(gentity_t *vehicle, gentity_t *player);
qboolean G_IsTurretUsable(gentity_t *turret, gentity_t *player);
void Use_BinaryMover(gentity_t *ent, gentity_t *other,
                            gentity_t *activator);
int G_CheckPointInsideTriggerMount(gentity_t *ent, const float *point,
                                          int *mountHintData);
int G_DObjGetWorldTagMatrix(gentity_t *ent, const char *tagName,
                                   float *matrix);
#if defined(__i386__)
typedef char com_parse_session_i386_size_check[
    (sizeof(com_parse_session_t) == 0x45c) ? 1 : -1];
#endif

static com_parse_session_t com_parseSessions[COM_MAX_PARSE_SESSIONS];
static int32_t com_parseSessionIndex;
static com_parse_session_t *com_parseCurrentSession = &com_parseSessions[0];
static char com_parseRestOfLineBuffer[COM_PARSE_TOKEN_SIZE];
static const char *com_parsePreviousTokenStart;
static const char *com_parseCurrentTokenStart;
static char q_vaTempBuffer[VA_TEMP_BUFFER_SIZE];
static char q_vaStringBuffer[VA_TEMP_BUFFER_SIZE];
static int q_vaStringOffset;
static float q_tvVectors[8][3];
static int q_tvIndex;

static const char *const com_parsePunctuation[] = {
    "+=", "-=", "*=", "/=", "&=", "|=", "++", "--", "&&", "||",
    "<=", ">=", "==", "!=", NULL
};

/* NOT_FROM_ORIGINAL_SOURCE: local vector copy helper for activation recovery. */
static void game_compat_g_vector_copy3(const float *src, float *dst)
{
    dst[0] = src[0];
    dst[1] = src[1];
    dst[2] = src[2];
}

/* NOT_FROM_ORIGINAL_SOURCE: local vector subtract helper for activation recovery. */
static void game_compat_g_vector_subtract3(const float *lhs, const float *rhs, float *out)
{
    out[0] = lhs[0] - rhs[0];
    out[1] = lhs[1] - rhs[1];
    out[2] = lhs[2] - rhs[2];
}

/* NOT_FROM_ORIGINAL_SOURCE: local dot-product helper for activation recovery.
 * Stock inlines the dot at the call site (0x5721d: 3-mul/2-add kept 80-bit,
 * stored to float); the caller consumes it as a stored float, so rounding here
 * on return is equivalent -> shim. */
static float game_compat_g_dot_product3(const float *lhs, const float *rhs)
{
#if EMULATE_X87
    return x87f_store_f32(x87f_add(x87f_add(
        x87f_mul(x87f_load_f32(lhs[0]), x87f_load_f32(rhs[0])),
        x87f_mul(x87f_load_f32(lhs[1]), x87f_load_f32(rhs[1]))),
        x87f_mul(x87f_load_f32(lhs[2]), x87f_load_f32(rhs[2]))));
#else
    return lhs[0] * rhs[0] + lhs[1] * rhs[1] + lhs[2] * rhs[2];
#endif
}

/* NOT_FROM_ORIGINAL_SOURCE: local entity bounds-center helper. */
static void game_compat_gentity_abs_center(const gentity_t *ent, vec3_t center)
{
    /* 0x57051-0x570d5 / 0x57413-0x5748b: the abs-bounds sums are stored
     * to center first, then scaled by 0.5f as a second float32 store. */
    center[0] = ent->absMin[0] + ent->absMax[0];
    center[1] = ent->absMin[1] + ent->absMax[1];
    center[2] = ent->absMin[2] + ent->absMax[2];
    center[0] *= 0.5f;
    center[1] *= 0.5f;
    center[2] *= 0.5f;
}

/* NOT_FROM_ORIGINAL_SOURCE: local predicate for weapon hint-string presence. */
static int game_compat_g_weapon_info_has_hint_string(const weaponInfo_t *weaponInfo)
{
    const char *hintString = weaponInfo->hintString;

    return hintString != NULL && hintString[0] != '\0';
}

/* NOT_FROM_ORIGINAL_SOURCE: names the binary mover in-use byte. */
static uint8_t *game_compat_g_binary_mover_in_use_byte(gentity_t *ent)
{
    return &ent->activeState;
}

/* NOT_FROM_ORIGINAL_SOURCE: factored repeated event-time stores. */
static void game_compat_g_set_entity_event_times(gentity_t *ent)
{
    ent->lastThinkTime = level.time;
    ent->eventTime2 = level.time;
}

/* VERIFIED_DECOMPILER(0x56d04, 66d04_G_Printf.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; varargs buffer and trap_Printf call match. */
void G_Printf(const char *format, ...)
{
    char buffer[GAME_PRINT_BUFFER_SIZE];
    va_list args;

    va_start(args, format);
    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this fixed
     * stack array; see original-bugs/game-common-formatting-stack-overflows.md. */
    vsprintf(buffer, format, args);
    va_end(args);

    trap_Printf(buffer);
}

/* VERIFIED_DECOMPILER(0x56d52, 66d52_G_DPrintf.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; developer-gated varargs buffer and trap_Printf call match. */
void G_DPrintf(const char *format, ...)
{
    char buffer[GAME_PRINT_BUFFER_SIZE];
    va_list args;

    if (g_developer.integer == 0) {
        return;
    }

    va_start(args, format);
    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this fixed
     * stack array; see original-bugs/game-common-formatting-stack-overflows.md. */
    vsprintf(buffer, format, args);
    va_end(args);

    trap_Printf(buffer);
}

/* VERIFIED_DECOMPILER(0x56dae, 66dae_G_Error.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; varargs buffer and trap_Error call match. */
void G_Error(const char *format, ...)
{
    char buffer[GAME_PRINT_BUFFER_SIZE];
    va_list args;

    va_start(args, format);
    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this fixed
     * stack array; see original-bugs/game-common-formatting-stack-overflows.md. */
    vsprintf(buffer, format, args);
    va_end(args);

    trap_Error(buffer);
}

/* VERIFIED_DECOMPILER(0x56dfc, 66dfc_G_Error_Localized.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; varargs buffer and localized trap error match. */
void G_Error_Localized(const char *format, ...)
{
    char buffer[GAME_PRINT_BUFFER_SIZE];
    va_list args;

    va_start(args, format);
    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this fixed
     * stack array; see original-bugs/game-common-formatting-stack-overflows.md. */
    vsprintf(buffer, format, args);
    va_end(args);

    trap_Error_Localized(buffer);
}

/* VERIFIED_DECOMPILER(0x58c90, 68c90_Com_Error.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; ignores code and forwards formatted text to G_Error("%s", buffer). */
void Com_Error(int code, const char *format, ...)
{
    char buffer[GAME_PRINT_BUFFER_SIZE];
    va_list args;

    (void)code;

    va_start(args, format);
    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this fixed
     * stack array; see original-bugs/game-common-formatting-stack-overflows.md. */
    vsprintf(buffer, format, args);
    va_end(args);

    G_Error("%s", buffer);
}

/* VERIFIED_DECOMPILER(0x58ce8, 68ce8_Com_Printf.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; formats and forwards through G_Printf("%s", buffer). */
void Com_Printf(const char *format, ...)
{
    char buffer[GAME_PRINT_BUFFER_SIZE];
    va_list args;

    va_start(args, format);
    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this fixed
     * stack array; see original-bugs/game-common-formatting-stack-overflows.md. */
    vsprintf(buffer, format, args);
    va_end(args);

    G_Printf("%s", buffer);
}

/* VERIFIED_DECOMPILER(0x58d40, 68d40_Com_DPrintf.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; developer-gated formatted G_Printf("%s", buffer). */
void Com_DPrintf(const char *format, ...)
{
    char buffer[GAME_PRINT_BUFFER_SIZE];
    va_list args;

    if (g_developer.integer == 0) {
        return;
    }

    va_start(args, format);
    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this fixed
     * stack array; see original-bugs/game-common-formatting-stack-overflows.md. */
    vsprintf(buffer, format, args);
    va_end(args);

    G_Printf("%s", buffer);
}

/* VERIFIED_DECOMPILER(0x9325e, a325e_Q_isprint.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; printable ASCII range check. */
int Q_isprint(int value)
{
    return value > 31 && value <= 126;
}

/* VERIFIED_DECOMPILER(0x93285, a3285_Q_islower.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; lowercase ASCII range check. */
int Q_islower(int value)
{
    return value > 'a' - 1 && value <= 'z';
}

/* VERIFIED_DECOMPILER(0x932ac, a32ac_Q_isupper.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; uppercase ASCII range check. */
int Q_isupper(int value)
{
    return value > 'A' - 1 && value <= 'Z';
}

/* VERIFIED_DECOMPILER(0x932d3, a32d3_Q_isalpha.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; direct alpha ASCII range checks. */
int Q_isalpha(int value)
{
    return (value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z');
}

/* VERIFIED_DECOMPILER(0x93308, a3308_Q_isnumeric.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; numeric ASCII range check. */
int Q_isnumeric(int value)
{
    return value > '0' - 1 && value <= '9';
}

/* VERIFIED_DECOMPILER(0x9332f, a332f_Q_isalphanumeric.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; alpha-or-numeric predicate call order. */
int Q_isalphanumeric(int value)
{
    return Q_isalpha(value) || Q_isnumeric(value);
}

/* VERIFIED_DECOMPILER(0x9337a, a337a_Q_isforfilename.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; filename character predicate. */
int Q_isforfilename(int value)
{
    return (Q_isalphanumeric(value) || value == '_' || value == '-') &&
           value != ' ';
}

/* VERIFIED_DECOMPILER(0x933c8, a33c8_Q_strrchr.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; last-character search with NUL handling. */
char *Q_strrchr(const char *string, int value)
{
    const char target = (char)value;
    const char *match = NULL;
    const char *cursor = string;

    while (*cursor != '\0') {
        if (*cursor == target) {
            match = cursor;
        }
        cursor++;
    }

    if (target == '\0') {
        match = cursor;
    }

    return (char *)match;
}

/* VERIFIED_DECOMPILER(0x93415, a3415_Q_strncpyz.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; bounded strncpy and forced terminator. */
void Q_strncpyz(char *dest, const char *src, int destsize)
{
    int lastIndex = destsize - 1;

    /* ORIGINAL_BINARY_BUG: a zero extent becomes a 0xffffffff copy count
     * followed by a terminator store through dest[-1]; see
     * original-bugs/coduomp-zero-size-string-output-underwrites.md. */
    strncpy(dest, src, (size_t)(uint32_t)lastIndex);
    dest[lastIndex] = '\0';
}

/* VERIFIED_DECOMPILER(0x935c2, a35c2_Q_strlwr.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; in-place tolower loop. */
char *Q_strlwr(char *string)
{
    char *cursor;

    for (cursor = string; *cursor != '\0'; cursor++) {
        *cursor = (char)tolower(coduo_ctype_signed_byte_arg(*cursor));
    }

    return string;
}

/* VERIFIED_DECOMPILER(0x93609, a3609_Q_strupr.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; in-place toupper loop. */
char *Q_strupr(char *string)
{
    char *cursor;

    for (cursor = string; *cursor != '\0'; cursor++) {
        *cursor = (char)toupper(coduo_ctype_signed_byte_arg(*cursor));
    }

    return string;
}

/* VERIFIED_DECOMPILER(0x93650, a3650_Q_strcat.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; length guard and Q_strncpyz append. */
void Q_strcat(char *dest, int size, const char *src)
{
    uint32_t usedBits = (uint32_t)strlen(dest);
    int used = coduo_int32_from_bits(usedBits);

    if (size <= used) {
        Com_Error(0, Q_STRCAT_OVERFLOW_ERROR);
    }

    Q_strncpyz(&dest[(size_t)usedBits], src,
               coduo_int32_from_bits((uint32_t)size - usedBits));
}

/* NOT_FROM_ORIGINAL_SOURCE: factored color-code predicate used by string cleaners. */
static int game_compat_q_is_color_string(const char *string)
{
    return string != NULL && string[0] == '^' && string[1] != '\0' &&
           string[1] != '^' && string[1] >= '0' && string[1] <= '9';
}

/* VERIFIED_DECOMPILER(0x936b5, a36b5_Q_DrawStrlen.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; visible length excluding caret color codes. */
int Q_DrawStrlen(const char *string)
{
    int length = 0;
    const char *cursor = string;

    while (*cursor != '\0') {
        if (game_compat_q_is_color_string(cursor)) {
            cursor = &cursor[2];
        } else {
            length = coduo_int32_from_bits((uint32_t)length + UINT32_C(1));
            cursor++;
        }
    }

    return length;
}

/* VERIFIED_DECOMPILER(0x9371d, a371d_Q_CleanStr.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; strips color codes and non-printable signed-char bytes. */
char *Q_CleanStr(char *string)
{
    char *out = string;
    char *in = string;

    while (*in != '\0') {
        signed char value = (signed char)*in;

        if (game_compat_q_is_color_string(in)) {
            in++;
        } else if (value > 31 && value != 127) {
            *out++ = (char)value;
        }
        in++;
    }

    *out = '\0';
    return string;
}

/* VERIFIED_DECOMPILER(0x937a5, a37a5_Q_CleanCharacter.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; CP1252 quote and negative-byte cleanup. */
int Q_CleanCharacter(int value)
{
    signed char c = (signed char)value;

    if (c == (signed char)CP1252_RIGHT_SINGLE_QUOTE) {
        return '\'';
    }

    if (c < 0) {
        return '.';
    }

    return c;
}

/* VERIFIED_DECOMPILER(0x93833, a3833_Q_strncasecmp.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; case-insensitive bounded equality compare. */
int Q_strncasecmp(const char *a, const char *b, int n)
{
    while (1) {
        int ca = (signed char)*a++;
        int cb = (signed char)*b++;

        n = coduo_int32_from_bits((uint32_t)n - UINT32_C(1));
        if (n == -1) {
            return 0;
        }

        if (ca != cb) {
            if (Q_islower(ca)) {
                ca -= 'a' - 'A';
            }
            if (Q_islower(cb)) {
                cb -= 'a' - 'A';
            }
            if (ca != cb) {
                return -1;
            }
        }

        if (ca == '\0') {
            return 0;
        }
    }
}

/* VERIFIED_DECOMPILER(0x938cd, a38cd_Q_strcasecmp.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; whole-string case-insensitive equality compare; decompiler renders integer 99999 as a nearby string pointer. */
int Q_strcasecmp(const char *a, const char *b)
{
    return Q_strncasecmp(a, b, Q_WHOLE_STRING);
}

/* VERIFIED_DECOMPILER(0x938ff, a38ff_va.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; 32000-byte temp buffer and ring wrap. */
const char *va(const char *format, ...)
{
    int length;
    char *out;
    va_list args;

    va_start(args, format);
    length = vsnprintf(q_vaTempBuffer, sizeof(q_vaTempBuffer), format, args);
    va_end(args);

    q_vaTempBuffer[VA_MAX_RESULT_LENGTH] = '\0';
    if (length < 0 || length > VA_MAX_RESULT_LENGTH) {
        Com_Error(1, VA_OVERRUN_ERROR);
    }

    if (q_vaStringOffset + length > VA_WRAP_LIMIT) {
        q_vaStringOffset = 0;
    }

    out = &q_vaStringBuffer[q_vaStringOffset];
    memcpy(out, q_vaTempBuffer, (size_t)length + 1u);
    q_vaStringOffset += length + 1;
    return out;
}

/* VERIFIED_DECOMPILER(0x939cb, a39cb_tv.c, VERIFY-GAMEHELPERS-2026-06-17): DATAFLOW_VERIFIED; eight-slot temporary vector ring. */
float *tv(float x, float y, float z)
{
    float *out = q_tvVectors[q_tvIndex];

    q_tvIndex = (q_tvIndex + 1) & 7;
    out[0] = x;
    out[1] = y;
    out[2] = z;

    return out;
}

/* VERIFIED_DECOMPILER(0x91a44, a1a44_Com_BeginParseSession.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; parse-session push and default fields at +0x400..+0x41c. */
void Com_BeginParseSession(const char *name)
{
    if (com_parseSessionIndex == COM_MAX_PARSE_SESSIONS - 1) {
        Com_Error(0, COM_PARSE_SESSION_OVERFLOW);
    }

    com_parseSessionIndex = coduo_int32_from_bits(
        (uint32_t)com_parseSessionIndex + UINT32_C(1));
    com_parseCurrentSession = &com_parseSessions[com_parseSessionIndex];
    com_parseCurrentSession->line = 1;
    com_parseCurrentSession->ungetToken = 0;
    com_parseCurrentSession->spaceDelimited = 1;
    com_parseCurrentSession->csv = 0;
    Q_strncpyz(com_parseCurrentSession->name, name,
               sizeof(com_parseCurrentSession->name));
}

/* VERIFIED_DECOMPILER(0x91aff, a1aff_Com_EndParseSession.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; parse-session pop and underflow guard. */
void Com_EndParseSession(void)
{
    if (com_parseSessionIndex == 0) {
        Com_Error(0, COM_PARSE_SESSION_UNDERFLOW);
    }

    com_parseSessionIndex = coduo_int32_from_bits(
        (uint32_t)com_parseSessionIndex - UINT32_C(1));
    com_parseCurrentSession = &com_parseSessions[com_parseSessionIndex];
}

/* VERIFIED_DECOMPILER(0x91b58, a1b58_Com_ResetParseSessions.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; resets current parse session pointer. */
void Com_ResetParseSessions(void)
{
    com_parseSessionIndex = 0;
    com_parseCurrentSession = &com_parseSessions[0];
}

/* VERIFIED_DECOMPILER(0x91b90, a1b90_Com_SetSpaceDelimited.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; sets space-delimited parse flag at +0x408. */
void Com_SetSpaceDelimited(int enabled)
{
    com_parseCurrentSession->spaceDelimited = enabled;
}

/* VERIFIED_DECOMPILER(0x91bb1, a1bb1_Com_SetCSV.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; sets CSV parse flag at +0x40c. */
void Com_SetCSV(int enabled)
{
    com_parseCurrentSession->csv = enabled;
}

/* VERIFIED_DECOMPILER(0x91bd2, a1bd2_Com_SetParseNegativeNumbers.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; sets negative-number parse flag at +0x410. */
void Com_SetParseNegativeNumbers(int enabled)
{
    com_parseCurrentSession->parseNegativeNumbers = enabled;
}

/* VERIFIED_DECOMPILER(0x91bf3, a1bf3_Com_GetCurrentParseLine.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; returns current parse line at +0x400. */
int Com_GetCurrentParseLine(void)
{
    return com_parseCurrentSession->line;
}

/* VERIFIED_DECOMPILER(0x91c11, a1c11_Com_ScriptError.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; formats parse error with file and line. */
void Com_ScriptError(const char *format, ...)
{
    char buffer[COM_PARSE_MESSAGE_BUFFER_SIZE];
    va_list args;

    va_start(args, format);
    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this fixed
     * stack array, then forwards it to a much smaller common formatter; see
     * original-bugs/game-common-formatting-stack-overflows.md. */
    vsprintf(buffer, format, args);
    va_end(args);

    Com_Error(1, COM_PARSE_ERROR_FORMAT, com_parseCurrentSession->name,
              com_parseCurrentSession->line, buffer);
}

/* VERIFIED_DECOMPILER(0x91c90, a1c90_Com_ScriptWarning.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; formats parse warning with file and line. */
void Com_ScriptWarning(const char *format, ...)
{
    char buffer[COM_PARSE_MESSAGE_BUFFER_SIZE];
    va_list args;

    va_start(args, format);
    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this fixed
     * stack array, then forwards it to a much smaller common formatter; see
     * original-bugs/game-common-formatting-stack-overflows.md. */
    vsprintf(buffer, format, args);
    va_end(args);

    Com_Printf(COM_PARSE_WARNING_FORMAT, com_parseCurrentSession->name,
               com_parseCurrentSession->line, buffer);
}

/* VERIFIED_DECOMPILER(0x91d07, a1d07_Com_UngetToken.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; one-token unget guard and token-position rewind. */
void Com_UngetToken(void)
{
    if (com_parseCurrentSession->ungetToken != 0) {
        Com_ScriptError(COM_PARSE_UNGET_TWICE_ERROR);
    }

    com_parseCurrentSession->ungetToken = 1;
    com_parseCurrentTokenStart = com_parsePreviousTokenStart;
}

/* VERIFIED_DECOMPILER(0x91d58, a1d58_Com_ParseSetMark.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; saves line, cursor, unget, and session saved parse fields. */
void Com_ParseSetMark(void *parse, com_parse_mark_t *mark)
{
    char **cursor = (char **)(parse);

    mark->line = com_parseCurrentSession->line;
    mark->parse = *cursor;
    mark->ungetToken = com_parseCurrentSession->ungetToken;
    mark->savedLine = com_parseCurrentSession->savedLine;
    mark->savedParse = com_parseCurrentSession->savedParse;
}

/* VERIFIED_DECOMPILER(0x91dbc, a1dbc_Com_ParseReturnToMark.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; restores line, cursor, unget, and session saved parse fields. */
void Com_ParseReturnToMark(void *parse, const com_parse_mark_t *mark)
{
    char **cursor = (char **)(parse);

    com_parseCurrentSession->line = mark->line;
    *cursor = mark->parse;
    com_parseCurrentSession->ungetToken = mark->ungetToken;
    com_parseCurrentSession->savedLine = mark->savedLine;
    com_parseCurrentSession->savedParse = mark->savedParse;
}

/* VERIFIED_DECOMPILER(0x91e20, a1e20_FUN_000a1e20.c, VERIFY-GAMEHELPERS-PARSE-2026-06-17): DATAFLOW_VERIFIED; skips whitespace and tracks newline presence. */
static char *Com_SkipParseWhitespace(char *text, int *hasNewLines)
{
    while (*text <= ' ') {
        if (*text == '\0') {
            return NULL;
        }

        if (*text == '\n') {
            com_parseCurrentSession->line = coduo_int32_from_bits(
                (uint32_t)com_parseCurrentSession->line + UINT32_C(1));
            *hasNewLines = 1;
        }

        text++;
    }

    return text;
}

/* VERIFIED_DECOMPILER(0x91e84, a1e84_Com_Compress.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; removes comments while preserving newlines. */
int Com_Compress(char *data)
{
    int size = 0;
    char *out = data;
    char *in = data;

    if (data != NULL) {
        while (*in != '\0') {
            if (*in == '\r' || *in == '\n') {
                *out++ = *in++;
                size = coduo_int32_from_bits((uint32_t)size + UINT32_C(1));
            } else if (in[0] == '/' && in[1] == '/') {
                while (*in != '\0' && *in != '\n') {
                    in++;
                }
            } else if (in[0] == '/' && in[1] == '*') {
                while (*in != '\0' && (in[0] != '*' || in[1] != '/')) {
                    if (*in == '\n') {
                        *out++ = '\n';
                        size = coduo_int32_from_bits(
                            (uint32_t)size + UINT32_C(1));
                    }
                    in++;
                }
                if (*in != '\0') {
                    in = &in[2];
                }
            } else {
                *out++ = *in++;
                size = coduo_int32_from_bits((uint32_t)size + UINT32_C(1));
            }
        }
    }

    /* ORIGINAL_BINARY_BUG: 0x91fb5 writes through the original NULL input;
     * see original-bugs/game-compress-null-output-write.md. */
    *out = '\0';
    return size;
}

/* VERIFIED_DECOMPILER(0x91fc0, a1fc0_Com_GetLastTokenPos.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; returns current token start pointer. */
char *Com_GetLastTokenPos(void)
{
    return (char *)com_parseCurrentTokenStart;
}

/* NOT_FROM_ORIGINAL_SOURCE: factored parser digit predicate. */
static int game_compat_com_parse_is_digit(char value)
{
    return value >= '0' && value <= '9';
}

/* NOT_FROM_ORIGINAL_SOURCE: factored parser alpha predicate. */
static int game_compat_com_parse_is_alpha(char value)
{
    return (value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z');
}

/* NOT_FROM_ORIGINAL_SOURCE: factored parser name-character predicate. */
static int game_compat_com_parse_is_name_char(char value)
{
    return game_compat_com_parse_is_alpha(value) || game_compat_com_parse_is_digit(value) ||
           value == '_' || value == '/' || value == '\\' ||
           value == ':' || value == '.';
}

/* NOT_FROM_ORIGINAL_SOURCE: factored parser token append with 1023-byte cap. */
static void game_compat_com_parse_append_char(int *tokenLength, char value)
{
    if (*tokenLength < COM_PARSE_TOKEN_SIZE - 1) {
        com_parseCurrentSession->token[*tokenLength] = value;
        (*tokenLength)++;
    }
}

/* VERIFIED_DECOMPILER(0x91fd8, a1fd8_FUN_000a1fd8.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; CSV tokenization, quote escaping, comma/newline cursor update. */
static char *Com_ParseCSV(char **parse, int allowLineBreaks)
{
    char *text = *parse;
    int tokenLength = 0;

    com_parseCurrentSession->token[0] = '\0';

    if (!allowLineBreaks) {
        if (*text == '\r' || *text == '\n') {
            return com_parseCurrentSession->token;
        }
    } else {
        while (*text == '\r' || *text == '\n') {
            text++;
        }
    }

    com_parsePreviousTokenStart = com_parseCurrentTokenStart;
    com_parseCurrentTokenStart = text;

    while (*text != '\0' && *text != ',' && *text != '\n') {
        if (*text == '\r') {
            text++;
        } else if (*text == '"') {
            text++;
            /* ORIGINAL_BINARY_BUG: 0x920be..0x920f2 has no NUL exit while
             * seeking the closing quote; see
             * original-bugs/game-csv-unterminated-quote-oob-read.md. */
            while (1) {
                while (*text != '"') {
                    game_compat_com_parse_append_char(&tokenLength, *text);
                    text++;
                }

                if (text[1] != '"') {
                    break;
                }

                game_compat_com_parse_append_char(&tokenLength, '"');
                text = &text[2];
            }
            text++;
        } else {
            game_compat_com_parse_append_char(&tokenLength, *text);
            text++;
        }
    }

    if (*text == '\0') {
        *parse = NULL;
    } else {
        if (*text != '\n') {
            text++;
        }
        *parse = text;
    }

    com_parseCurrentSession->token[tokenLength] = '\0';
    return com_parseCurrentSession->token;
}

/* VERIFIED_DECOMPILER(0x9216a, a216a_FUN_000a216a.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; main token parser, comments, strings, numbers, names, punctuation. */
static char *Com_ParseInternal(char **parse, int allowLineBreaks)
{
    char *text;
    char value;
    int hasNewLines = 0;
    int tokenLength = 0;

    if (parse == NULL) {
        Com_Error(0, COM_PARSE_NULL_DATA_ERROR);
    }

    text = *parse;
    com_parseCurrentSession->token[0] = '\0';

    if (text == NULL) {
        *parse = NULL;
        return com_parseCurrentSession->token;
    }

    com_parseCurrentSession->savedLine = com_parseCurrentSession->line;
    com_parseCurrentSession->savedParse = text;

    if (com_parseCurrentSession->csv) {
        return Com_ParseCSV(parse, allowLineBreaks);
    }

    while ((text = Com_SkipParseWhitespace(text, &hasNewLines)) != NULL) {
        if (hasNewLines && !allowLineBreaks) {
            *parse = text;
            return com_parseCurrentSession->token;
        }

        value = *text;
        if (value == '/' && text[1] == '/') {
            while (*text != '\0' && *text != '\n') {
                text++;
            }
            continue;
        }

        if (value == '/' && text[1] == '*') {
            text = &text[2];
            while (*text != '\0' && (*text != '*' || text[1] != '/')) {
                if (*text == '\n') {
                    com_parseCurrentSession->line = coduo_int32_from_bits(
                        (uint32_t)com_parseCurrentSession->line + UINT32_C(1));
                }
                text++;
            }
            if (*text != '\0') {
                text = &text[2];
            }
            continue;
        }

        com_parsePreviousTokenStart = com_parseCurrentTokenStart;
        com_parseCurrentTokenStart = text;

        if (value == '"') {
            text++;
            while (1) {
                char *next;

                value = *text;
                next = &text[1];

                if (value == '\\' && *next == '"') {
                    value = *next;
                    text = &text[2];
                } else {
                    if (value == '"' || value == '\0') {
                        /* ORIGINAL_BINARY_BUG: when value is NUL, 0x92375
                         * publishes next (one byte past the terminator); a
                         * continuing parser consumer then reads out of bounds.
                         * See original-bugs/game-unterminated-quoted-token-oob-read.md. */
                        com_parseCurrentSession->token[tokenLength] = '\0';
                        *parse = next;
                        return com_parseCurrentSession->token;
                    }
                    text = next;
                    /* ORIGINAL_BINARY_BUG: 0x9238b tests the advanced cursor,
                     * missing a newline just consumed after the opening quote
                     * or an escaped quote; see
                     * original-bugs/game-quoted-leading-newline-line-count.md. */
                    if (*next == '\n') {
                        com_parseCurrentSession->line = coduo_int32_from_bits(
                            (uint32_t)com_parseCurrentSession->line +
                            UINT32_C(1));
                    }
                }

                game_compat_com_parse_append_char(&tokenLength, value);
            }
        }

        if (com_parseCurrentSession->spaceDelimited) {
            do {
                game_compat_com_parse_append_char(&tokenLength, value);
                text++;
                value = *text;
            } while (value > ' ');

            com_parseCurrentSession->token[tokenLength] = '\0';
            *parse = text;
            return com_parseCurrentSession->token;
        }

        if (game_compat_com_parse_is_digit(value) ||
            (com_parseCurrentSession->parseNegativeNumbers &&
             value == '-' && game_compat_com_parse_is_digit(text[1])) ||
            (value == '.' && game_compat_com_parse_is_digit(text[1]))) {
            do {
                game_compat_com_parse_append_char(&tokenLength, value);
                text++;
                value = *text;
            } while (game_compat_com_parse_is_digit(value) || value == '.');

            if (value == 'e' || value == 'E') {
                /* ORIGINAL_BINARY_BUG: 0x92543..0x9256d consumes a mandatory
                 * exponent byte even when it is NUL, then reads the following
                 * byte; see
                 * original-bugs/game-parse-malformed-exponent-oob-read.md. */
                game_compat_com_parse_append_char(&tokenLength, value);
                text++;
                value = *text;

                if (value == '-' || value == '+') {
                    game_compat_com_parse_append_char(&tokenLength, value);
                    text++;
                    value = *text;
                }

                game_compat_com_parse_append_char(&tokenLength, value);
                text++;
                value = *text;

                while (game_compat_com_parse_is_digit(value)) {
                    game_compat_com_parse_append_char(&tokenLength, value);
                    text++;
                    value = *text;
                }
            }

            com_parseCurrentSession->token[tokenLength] = '\0';
            *parse = text;
            return com_parseCurrentSession->token;
        }

        if (game_compat_com_parse_is_alpha(value) || value == '_' ||
            value == '/' || value == '\\') {
            do {
                game_compat_com_parse_append_char(&tokenLength, value);
                text++;
                value = *text;
            } while (game_compat_com_parse_is_name_char(value));

            com_parseCurrentSession->token[tokenLength] = '\0';
            *parse = text;
            return com_parseCurrentSession->token;
        }

        for (const char *const *punctuation = com_parsePunctuation;
             *punctuation != NULL; punctuation++) {
            const size_t length = strlen(*punctuation);

            if (strncmp(text, *punctuation, length) == 0) {
                memcpy(com_parseCurrentSession->token, *punctuation, length);
                com_parseCurrentSession->token[length] = '\0';
                *parse = &text[length];
                return com_parseCurrentSession->token;
            }
        }

        com_parseCurrentSession->token[0] = *text;
        com_parseCurrentSession->token[1] = '\0';
        *parse = &text[1];
        return com_parseCurrentSession->token;
    }

    *parse = NULL;
    return com_parseCurrentSession->token;
}

/* 0x92768 Com_Parse */
/* VERIFIED_DECOMPILER(0x92768, a2768_Com_Parse.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; unget-token rewind and parse with line breaks; binary leaves Com_ParseInternal result in eax despite Ghidra void inference. */
char *Com_Parse(void *parse)
{
    char **cursor = (char **)(parse);

    if (com_parseCurrentSession->ungetToken != 0) {
        com_parseCurrentSession->ungetToken = 0;
        *cursor = com_parseCurrentSession->savedParse;
        com_parseCurrentSession->line = com_parseCurrentSession->savedLine;
    }

    return Com_ParseInternal(cursor, 1);
}

/* 0x927db Com_ParseOnLine */
/* VERIFIED_DECOMPILER(0x927db, a27db_Com_ParseOnLine.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; unget-token handling and no-linebreak parse. */
char *Com_ParseOnLine(void *parse)
{
    char **cursor = (char **)(parse);

    if (com_parseCurrentSession->ungetToken != 0) {
        com_parseCurrentSession->ungetToken = 0;
        if (com_parseCurrentSession->spaceDelimited == 0) {
            return com_parseCurrentSession->token;
        }

        *cursor = com_parseCurrentSession->savedParse;
        com_parseCurrentSession->line = com_parseCurrentSession->savedLine;
    }

    return Com_ParseInternal(cursor, 0);
}

/* 0x9286e Com_MatchToken */
/* VERIFIED_DECOMPILER(0x9286e, a286e_Com_MatchToken.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; parse, compare, and warning/error dispatch. */
void Com_MatchToken(void *parse, const char *match, int warning)
{
    char *token = Com_Parse(parse);

    if (strcmp(token, match) != 0) {
        if (warning == 0) {
            Com_ScriptError(COM_MATCH_TOKEN_ERROR_FORMAT, token, match);
        } else {
            Com_ScriptWarning(COM_MATCH_TOKEN_ERROR_FORMAT, token, match);
        }
    }
}

/* 0x928ea Com_SkipBracedSection */
/* VERIFIED_DECOMPILER(0x928ea, a28ea_Com_SkipBracedSection.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; brace-depth scan and outer-brace result. */
int Com_SkipBracedSection(void *parse, int depth)
{
    char **cursor = (char **)(parse);
    int skippedOuterBrace = 0;
    int braceDepth = 0;

    do {
        char *token = Com_Parse(cursor);

        if (token[1] == '\0') {
            if (token[0] == '{') {
                if (braceDepth == depth) {
                    skippedOuterBrace = 1;
                } else {
                    braceDepth = coduo_int32_from_bits(
                        (uint32_t)braceDepth + UINT32_C(1));
                }
            } else if (token[0] == '}') {
                braceDepth = coduo_int32_from_bits(
                    (uint32_t)braceDepth - UINT32_C(1));
            }
        }
    } while (braceDepth != 0 && *cursor != NULL);

    return skippedOuterBrace;
}

/* 0x92965 Com_SkipRestOfLine */
/* VERIFIED_DECOMPILER(0x92965, a2965_Com_SkipRestOfLine.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; cursor advance to newline/NUL and line count. */
void Com_SkipRestOfLine(void *parse)
{
    char **cursor = (char **)(parse);
    char *text = *cursor;

    if (text == NULL) {
        return;
    }

    while (*text != '\0') {
        const char value = *text++;

        if (value == '\n') {
            com_parseCurrentSession->line = coduo_int32_from_bits(
                (uint32_t)com_parseCurrentSession->line + UINT32_C(1));
            break;
        }
    }

    *cursor = text;
}

/* 0x929bf Com_ParseRestOfLine */
/* VERIFIED_DECOMPILER(0x929bf, a29bf_Com_ParseRestOfLine.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; one-line token join into 1024-byte buffer. */
char *Com_ParseRestOfLine(void *parse)
{
    char *token;

    com_parseRestOfLineBuffer[0] = '\0';

    while (1) {
        token = Com_ParseOnLine(parse);
        if (token[0] == '\0') {
            break;
        }

        if (com_parseRestOfLineBuffer[0] != '\0') {
            Q_strcat(com_parseRestOfLineBuffer,
                     sizeof(com_parseRestOfLineBuffer), " ");
        }
        Q_strcat(com_parseRestOfLineBuffer, sizeof(com_parseRestOfLineBuffer),
                 token);
    }

    return com_parseRestOfLineBuffer;
}

/* 0x92a44 Com_ParseFloat */
/* VERIFIED_DECOMPILER(0x92a44, a2a44_Com_ParseFloat.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; empty-token zero and atof float conversion. */
float Com_ParseFloat(void *parse)
{
    char *token = Com_Parse(parse);

    if (token[0] == '\0') {
        return 0.0f;
    }

    return (float)atof(token);
}

/* 0x92a94 Com_ParseInt */
/* VERIFIED_DECOMPILER(0x92a94, a2a94_Com_ParseInt.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; empty-token zero and atoi conversion. */
int Com_ParseInt(void *parse)
{
    char *token = Com_Parse(parse);

    if (token[0] == '\0') {
        return 0;
    }

    return atoi(token);
}

/* 0x92adc Com_Parse1DMatrix */
/* VERIFIED_DECOMPILER(0x92adc, a2adc_Com_Parse1DMatrix.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; parenthesized float vector parse. */
void Com_Parse1DMatrix(void *parse, int x, float *m)
{
    Com_MatchToken(parse, "(", 0);

    for (int i = 0; i < x; i++) {
        m[i] = (float)atof(Com_Parse(parse));
    }

    Com_MatchToken(parse, ")", 0);
}

/* 0x92b73 Com_Parse2DMatrix */
/* VERIFIED_DECOMPILER(0x92b73, a2b73_Com_Parse2DMatrix.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; parenthesized row loop. */
void Com_Parse2DMatrix(void *parse, int y, int x, float *m)
{
    Com_MatchToken(parse, "(", 0);

    for (int i = 0; i < y; i++) {
        const uint32_t elementOffset = (uint32_t)i * (uint32_t)x;

        Com_Parse1DMatrix(parse, x, &m[(size_t)elementOffset]);
    }

    Com_MatchToken(parse, ")", 0);
}

/* 0x92c00 Com_Parse3DMatrix */
/* VERIFIED_DECOMPILER(0x92c00, a2c00_Com_Parse3DMatrix.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; parenthesized plane loop. */
void Com_Parse3DMatrix(void *parse, int z, int y, int x, float *m)
{
    Com_MatchToken(parse, "(", 0);

    for (int i = 0; i < z; i++) {
        const uint32_t elementOffset =
            (uint32_t)i * (uint32_t)y * (uint32_t)x;

        Com_Parse2DMatrix(parse, y, x, &m[(size_t)elementOffset]);
    }

    Com_MatchToken(parse, ")", 0);
}

/* 0x92d0d Com_SkipPath */
/* VERIFIED_DECOMPILER(0x92d0d, a2d0d_Com_SkipPath.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; last slash basename scan. */
char *Com_SkipPath(char *path)
{
    char *base = path;

    while (*path != '\0') {
        if (*path == '/') {
            base = &path[1];
        }
        path++;
    }

    return base;
}

/* 0x92d3c Com_StripExtension */
/* VERIFIED_DECOMPILER(0x92d3c, a2d3c_Com_StripExtension.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; copy through first dot or NUL. */
void Com_StripExtension(const char *in, char *out)
{
    while (*in != '\0' && *in != '.') {
        *out++ = *in++;
    }
    *out = '\0';
}

/* 0x92d71 Com_StripFilename */
/* VERIFIED_DECOMPILER(0x92d71, a2d71_Com_StripFilename.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; strlen-sized copy then basename truncation. */
void Com_StripFilename(const char *in, char *out)
{
    /* ORIGINAL_BINARY_BUG: stock passes strlen(in), so Q_strncpyz drops the
     * last byte and an empty input expands its -1 copy count to 0xffffffff;
     * see original-bugs/game-path-helper-empty-input-accesses.md. */
    Q_strncpyz(out, in, (int)strlen(in));
    *Com_SkipPath(out) = '\0';
}

/* 0x92dbe Com_DefaultExtension */
/* VERIFIED_DECOMPILER(0x92dbe, a2dbe_Com_DefaultExtension.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; suffix scan, 64-byte temp copy, and append. */
void Com_DefaultExtension(char *path, int maxSize, const char *extension)
{
    char oldPath[64];
    /* ORIGINAL_BINARY_BUG: 0x92dd0..0x92de5 forms and dereferences path - 1
     * for an empty string; see
     * original-bugs/game-path-helper-empty-input-accesses.md. */
    char *cursor = &path[strlen(path) - 1];

    while (*cursor != '/' && cursor != path) {
        if (*cursor == '.') {
            return;
        }
        cursor--;
    }

    Q_strncpyz(oldPath, path, sizeof(oldPath));
    Com_sprintf(path, (size_t)maxSize, "%s%s", oldPath, extension);
}

/* 0x92e4f Com_BitCheck */
/* VERIFIED_DECOMPILER(0x92e4f, a2e4f_Com_BitCheck.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; 32-bit word/bit test. */
int Com_BitCheck(const uint32_t *bits, int bit)
{
    return (int)((bits[bit >> 5] >> ((uint32_t)bit & 31u)) & 1u);
}

/* 0x92e72 Com_BitSet */
/* VERIFIED_DECOMPILER(0x92e72, a2e72_Com_BitSet.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; 32-bit word/bit set. */
void Com_BitSet(uint32_t *bits, int bit)
{
    bits[bit >> 5] |= 1u << ((uint32_t)bit & 31u);
}

/* 0x92eb6 Com_BitClear */
/* VERIFIED_DECOMPILER(0x92eb6, a2eb6_Com_BitClear.c, VERIFY-GAMEHELPERS-SMALL-2026-06-17): DATAFLOW_VERIFIED; 32-bit word/bit clear. */
void Com_BitClear(uint32_t *bits, int bit)
{
    bits[bit >> 5] &= ~(1u << ((uint32_t)bit & 31u));
}

/* 0x56a7d FUN_00066a7d */
/* VERIFIED_DECOMPILER(0x56a7d, 66a7d_FUN_00066a7d.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; float-plus-half integer conversion. */
int Game_RoundFloatPlusHalf(float value)
{
    /* 0x56a8f-0x56aac: the +0.5f sum is truncated straight from the x87
     * register (fistp direct), with no float32 rounding of the sum -> shim. */
#if EMULATE_X87
    return x87f_store_i32_trunc(
        x87f_add(x87f_load_f32(value), x87f_load_f32(0.5f)));
#else
    return game_compat_int32_from_long_double_trunc(
        (long double)value + (long double)0.5f);
#endif
}

/* 0x56e4a FUN_00066e4a */
/* VERIFIED_DECOMPILER(0x56e4a, 66e4a_FUN_00066e4a.c, VERIFY-GAME-HELPERS-ACTIVATE-PACKET-2026-06-17): DATAFLOW_VERIFIED - qsort comparator signature, score offset, float subtraction, and truncate-to-int return checked. */
int G_CompareActivateEntScores(const void *lhs, const void *rhs)
{
    const activate_candidate_t *a = (const activate_candidate_t *)lhs;
    const activate_candidate_t *b = (const activate_candidate_t *)rhs;

    /* ORIGINAL_BINARY_BUG: truncating the float difference makes equal and
     * nontransitive qsort results for ordinary close scores; see
     * original-bugs/game-activate-score-comparator.md.
     * 0x56e62..0x56e7a truncates straight from the x87 register (fistp
     * direct), with no float32 rounding of the difference. */
#if EMULATE_X87
    return x87f_store_i32_trunc(
        x87f_sub(x87f_load_f32(a->score), x87f_load_f32(b->score)));
#else
    return game_compat_int32_from_long_double_trunc(
        (long double)a->score - (long double)b->score);
#endif
}

/* ------------------------------------------------------------------ */
/*  0x56e85 G_GetActivateEnt                                          */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x56e85, 66e85_G_GetActivateEnt.c, VERIFY-GAME-HELPERS-ACTIVATE-PACKET-2026-06-17): DATAFLOW_VERIFIED - activation box query, candidate filters, vehicle/entity targeting traces, scoring penalties, two qsorts, occlusion pruning, and return count checked. */
int G_GetActivateEnt(gentity_t *ent, activate_candidate_t *candidates)
{
    gclient_t *client = ent->client;
    vec3_t forward;
    vec3_t muzzle;
    vec3_t searchMins;
    vec3_t searchMaxs;
    int entityNums[ACTIVATE_MAX_CANDIDATES];
    int entityCount;
    int candidateCount = 0;
    int unusableItemCount = 0;
    int occludedCount = 0;
    int index;

    AngleVectors(client->ps.viewAngles, forward, NULL, NULL);
    CalcMuzzlePoint(ent, muzzle);

    searchMins[0] = muzzle[0] - ACTIVATE_SEARCH_XY;
    searchMins[1] = muzzle[1] - ACTIVATE_SEARCH_XY;
    searchMins[2] = muzzle[2] - ACTIVATE_SEARCH_Z;
    searchMaxs[0] = muzzle[0] + ACTIVATE_SEARCH_XY;
    searchMaxs[1] = muzzle[1] + ACTIVATE_SEARCH_XY;
    searchMaxs[2] = muzzle[2] + ACTIVATE_SEARCH_Z;

    entityCount = trap_EntitiesInBox(searchMins, searchMaxs, entityNums,
                                     ACTIVATE_MAX_CANDIDATES,
                                     ACTIVATE_ENTITY_CONTENTS);
    for (index = 0; index < entityCount; index++) {
        gentity_t *candidate = &g_entities[entityNums[index]];
        vec3_t center;
        vec3_t direction;
        float distance;
        float dot;
        float baseScore;

        if (candidate == ent ||
            (candidate->s_eType != ET_ITEM &&
             (candidate->scriptContents & ACTIVATE_ENTITY_CONTENTS) == 0)) {
            continue;
        }

        game_compat_gentity_abs_center(candidate, center);
        if (candidate->s_eType == ET_VEHICLE) {
            trace_t trace;

            /* Stock 0x570xx..0x5712a: forward[k]*DIST kept 80-bit, + muzzle[k],
             * one store -> shim (mul+add). */
#if EMULATE_X87
            for (int k = 0; k < 3; k++) {
                center[k] = x87f_store_f32(x87f_add(
                    x87f_mul(x87f_load_f32(forward[k]),
                             x87f_load_f32(ACTIVATE_MAX_DISTANCE)),
                    x87f_load_f32(muzzle[k])));
            }
#else
            center[0] = muzzle[0] + forward[0] * ACTIVATE_MAX_DISTANCE;
            center[1] = muzzle[1] + forward[1] * ACTIVATE_MAX_DISTANCE;
            center[2] = muzzle[2] + forward[2] * ACTIVATE_MAX_DISTANCE;
#endif
            trap_Trace(&trace, muzzle, vec3_origin, vec3_origin, center,
                       client->ps.psClientNum, ACTIVATE_VEHICLE_TRACE_MASK);
            if (trace.entityNum != candidate->s_number) {
                continue;
            }
            game_compat_g_vector_subtract3(trace.endpos, muzzle, direction);
        } else {
            game_compat_g_vector_subtract3(center, muzzle, direction);
        }

        distance = VectorNormalize(direction);
        dot = game_compat_g_dot_product3(direction, forward);
        if (distance > ACTIVATE_MAX_DISTANCE || dot <= 0.0f ||
            dot < ACTIVATE_MIN_DOT) {
            continue;
        }

        /* Stock 0x5727e..0x572a0: 1.0 - (dot-MIN)/(1.0-MIN), the divide kept
         * 80-bit through the outer subtract, one store -> shim. */
#if EMULATE_X87
        baseScore = x87f_store_f32(x87f_sub(
            x87f_load_f32(1.0f),
            x87f_div(
                x87f_sub(x87f_load_f32(dot), x87f_load_f32(ACTIVATE_MIN_DOT)),
                x87f_sub(x87f_load_f32(1.0f), x87f_load_f32(ACTIVATE_MIN_DOT)))));
#else
        baseScore = 1.0f - (dot - ACTIVATE_MIN_DOT) /
                    (1.0f - ACTIVATE_MIN_DOT);
#endif
        /* 0x572a0/0x572c2: the falloff factor is stored to a float32 slot
         * before the distance-scale multiply, which rounds again. */
        baseScore *= ACTIVATE_MAX_DISTANCE + ACTIVATE_MAX_DISTANCE;
        if (candidate->s_eType == ET_ITEM &&
            BG_CanItemBeGrabbed(candidate, client, 0) == 0) {
            baseScore += ACTIVATE_UNUSABLE_ITEM_PENALTY;
            unusableItemCount++;
        }

        candidates[candidateCount].ent = candidate;
        candidates[candidateCount].score = baseScore + distance;
        candidateCount++;
    }

    qsort(candidates, (size_t)candidateCount, sizeof(candidates[0]),
          G_CompareActivateEntScores);
    candidateCount -= unusableItemCount;

    for (index = 0; index < candidateCount; index++) {
        gentity_t *candidate = candidates[index].ent;
        vec3_t center;
        trace_t trace;

        if (candidate->s_eType == ET_VEHICLE) {
            break;
        }

        game_compat_gentity_abs_center(candidate, center);
        if (candidate->s_eType == ET_TURRET) {
            float tagMatrix[16];

            if (G_DObjGetWorldTagMatrix(candidate, "tag_aim",
                                        tagMatrix) != 0) {
                game_compat_g_vector_copy3(&tagMatrix[12], center);
            }
        }

        trap_Trace(&trace, muzzle, vec3_origin, vec3_origin, center,
                   client->ps.psClientNum, ACTIVATE_VIS_TRACE_MASK);
        if (trace.entityNum != ENTITYNUM_WORLD) {
            break;
        }

        candidates[index].score += ACTIVATE_OCCLUDED_PENALTY;
        occludedCount++;
    }

    qsort(candidates, (size_t)candidateCount, sizeof(candidates[0]),
          G_CompareActivateEntScores);
    return candidateCount - occludedCount;
}

/* ------------------------------------------------------------------ */
/*  0x651ac G_Activate                                                */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x651ac, 751ac_G_Activate.c, VERIFY-GAME-HELPERS-ACTIVATE-PACKET-2026-06-17): DATAFLOW_VERIFIED - stationary and lock gates, active byte stores, low-word team token test, team-master redirect, and Use_BinaryMover argument order checked. */
void G_Activate(gentity_t *ent, gentity_t *activator)
{
    gentity_t *target;

    if (ent->apos.trType != TR_STATIONARY ||
        ent->pos.trType != TR_STATIONARY ||
        (*game_compat_g_binary_mover_in_use_byte(ent)) != 0 ||
        ent->doorLocked != 0) {
        return;
    }

    target = ent;
    if (ent->teamMaster != NULL &&
        (uint16_t)ent->teamName != 0 &&
        ent != ent->teamMaster) {
        target = ent->teamMaster;
    }

    (*game_compat_g_binary_mover_in_use_byte(target)) = 1;
    Use_BinaryMover(target, activator, activator);
}

/* ------------------------------------------------------------------ */
/*  0x575bb G_CheckForCursorHints                                     */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x575bb, 675bb_G_CheckForCursorHints.c, VERIFY-GAME-HELPERS-ACTIVATE-PACKET-2026-06-17): DATAFLOW_VERIFIED - cursor reset stores, weapon-class gates, mount probe, activate candidate loop, hint/string/entity fields, item/mover/turret/vehicle branches, and early returns checked. */
void G_CheckForCursorHints(gentity_t *ent)
{
    activate_candidate_t candidates[ACTIVATE_MAX_CANDIDATES];
    gclient_t *client = ent->client;
    int hint = CURSOR_HINT_OFF;
    int hintData = 0;
    int hintString = -1;
    int candidateCount = 0;
    const weaponInfo_t *weaponInfo;
    int weaponClass;
    int index;

    client->ps.serverCursorHint = CURSOR_HINT_OFF;
    client->ps.serverCursorHintVal = 0;
    client->ps.cursorHintEntNum = ENTITYNUM_NONE;

    if (ent->health <= 0 ||
        (*game_compat_g_binary_mover_in_use_byte(ent)) != 0) {
        return;
    }

    client->ps.serverCursorHintString = -1;
    weaponInfo = (const weaponInfo_t *)BG_GetInfoForWeapon(client->ps.currentWeapon);
    weaponClass = weaponInfo->weaponClass;
    if (((client->ps.playerStateFlags & CURSOR_STANCE_FLAG) != 0 &&
         weaponClass == WEAPCLASS_LMG) ||
        (client->ps.entityStateFlags & CURSOR_PS_FLAG_IN_VEHICLE) != 0) {
        return;
    }

    candidateCount = G_GetActivateEnt(ent, candidates);
    if (weaponClass == WEAPCLASS_LMG) {
        vec3_t yawAngles = {0.0f, client->ps.viewAngles[1], 0.0f};
        vec3_t forward;
        vec3_t mountPoint;
        int mountHintData = 0;
        int contents;

        AngleVectors(yawAngles, forward, NULL, NULL);
        /* Stock 0x577ca..0x577e0: forward[k]*15.0 kept 80-bit, + currentOrigin[k],
         * one store -> shim (mul+add). */
#if EMULATE_X87
        for (int k = 0; k < 3; k++) {
            mountPoint[k] = x87f_store_f32(x87f_add(
                x87f_mul(x87f_load_f32(forward[k]), x87f_load_f32(15.0f)),
                x87f_load_f32(ent->currentOrigin[k])));
        }
#else
        mountPoint[0] = ent->currentOrigin[0] + forward[0] * 15.0f;
        mountPoint[1] = ent->currentOrigin[1] + forward[1] * 15.0f;
        mountPoint[2] = ent->currentOrigin[2] + forward[2] * 15.0f;
#endif
        /* 0x577e6-0x577f4: the +1.0f lift is a second float32 store on
         * the already-rounded mountPoint[2] (single add -> native). */
        mountPoint[2] += 1.0f;

        contents = trap_PointContents(mountPoint, PASS_ENTITY_NONE,
                                      CURSOR_MOUNT_CONTENTS);
        if (contents != 0 ||
            G_CheckPointInsideTriggerMount(ent, mountPoint, &mountHintData) != 0) {
            hint = CURSOR_HINT_LMG;
            hintData = mountHintData;
            if (game_compat_g_weapon_info_has_hint_string(weaponInfo)) {
                hintString = weaponInfo->hintStringIndex;
            }
        }

        client->ps.serverCursorHint = hint;
        client->ps.serverCursorHintVal = hintData;
        client->ps.serverCursorHintString = hintString;
    }

    for (index = 0; index < candidateCount; index++) {
        gentity_t *candidate = candidates[index].ent;

        client->ps.cursorHintEntNum = (uint16_t)candidate->s_number;
        if (candidate == NULL) {
            break;
        }

        if (candidate->s_number == ENTITYNUM_WORLD) {
            if ((client->ps.cursorHintFlags & CURSOR_FLAG_WORLD_HINT) != 0 &&
                (client->ps.playerStateFlags &
                 CURSOR_PLAYERSTATE_WORLD_HINT_BLOCKED) == 0) {
                hint = CURSOR_HINT_LADDER;
            }
            break;
        }

        if (candidate->client != NULL) {
            break;
        }

        if (candidate->s_eType == ET_GENERAL) {
            if (candidate->scriptClassname == scr_const_trigger_use) {
                hint = candidate->cursorHint;
                if (candidate->cursorHint != 0 &&
                    candidate->hintStringIndex != CURSOR_HINT_STRING_INHERIT) {
                    hintString = candidate->hintStringIndex;
                }
            }
        } else if (candidate->s_eType == ET_TURRET) {
            if (G_IsTurretUsable(candidate, ent) != 0) {
                const weaponInfo_t *turretWeaponInfo =
                    (const weaponInfo_t *)BG_GetInfoForWeapon(
                        candidate->entityStateWeapon);

                hint = CURSOR_HINT_MG42;
                if (game_compat_g_weapon_info_has_hint_string(turretWeaponInfo)) {
                    hintString = turretWeaponInfo->hintStringIndex;
                }
            } else {
                continue;
            }
        } else if (candidate->s_eType == ET_VEHICLE) {
            if (G_IsVehicleUsable(candidate, ent) != 0) {
                const vehicle_state_t *vehicle =
                    (const vehicle_state_t *)candidate->vehicle;

                hint = CURSOR_HINT_ACTIVATE;
                hintString = vehicle->hintStringIndex;
            } else {
                continue;
            }
        } else if (candidate->s_eType == ET_ITEM) {
            const gitem_t *itemInfo = (const gitem_t *)candidate->itemInfo;
            itemType_t itemType = itemInfo->type;
            int itemWeapon = itemInfo->weapon;

            if (itemType == IT_WEAPON) {
                if (Com_BitCheck(client->ps.weaponBits, itemWeapon) == 0) {
                    hint = itemWeapon + CURSOR_HINT_ITEM_BASE;
                } else {
                    hint = itemWeapon + CURSOR_HINT_OWNED_WEAPON_BASE;
                }
            } else if (itemType == IT_AMMO) {
                hint = itemWeapon + CURSOR_HINT_OWNED_WEAPON_BASE;
            } else if (itemType == IT_HEALTH) {
                hint = CURSOR_HINT_HEALTH;
            }
        } else if (candidate->s_eType == ET_MOVER) {
            uint16_t classname = candidate->scriptClassname;
            uint8_t moverState =
                candidate->moverState;
            uint8_t moverFlags =
                (uint8_t)candidate->flags;

            if (classname == scr_const_func_door_rotating) {
                if (moverState == 7 ||
                    (moverState == 8 && (moverFlags & 0x80u) != 0)) {
                    hint = CURSOR_HINT_DOOR;
                    if (candidate->doorLocked != 0) {
                        hint = CURSOR_HINT_DOOR_LOCKED;
                    }
                }
            } else if (classname == scr_const_func_door) {
                if (moverState == 0 ||
                    (moverState == 1 && (moverFlags & 0x80u) != 0)) {
                    hint = CURSOR_HINT_DOOR;
                    if (candidate->doorLocked != 0) {
                        hint = CURSOR_HINT_DOOR_LOCKED;
                    }
                }
            }
        }

        if (candidate->cursorHint > CURSOR_HINT_OFF &&
            hint != CURSOR_HINT_OFF) {
            hint = candidate->cursorHint;
        }
        break;
    }

    client->ps.serverCursorHint = hint;
    client->ps.serverCursorHintVal = hintData;
    client->ps.serverCursorHintString = hintString;
    if (client->ps.serverCursorHint == CURSOR_HINT_OFF) {
        client->ps.cursorHintEntNum = ENTITYNUM_NONE;
    }
}

/* ------------------------------------------------------------------ */
/*  0x57c8b G_CheckForPreventFriendlyFire                             */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x57c8b, 67c8b_G_CheckForPreventFriendlyFire.c, VERIFY-GAME-HELPERS-ACTIVATE-PACKET-2026-06-17): DATAFLOW_VERIFIED - lookAtEntity clear, active-state gate, muzzle/end vectors, priority-map short-circuit, trace masks, trigger_friendlyfire classname gate, and side effects checked. */
void G_CheckForPreventFriendlyFire(gentity_t *ent)
{
    helper_weapon_muzzle_t muzzle;
    const weaponInfo_t *weaponInfo;
    int currentWeapon;
    const uint8_t *priorityMap;
    vec3_t end;
    trace_t trace;

    ent->client->lookAtEntity = NULL;
    if ((*game_compat_g_binary_mover_in_use_byte(ent)) != 0) {
        return;
    }

    CalcMuzzlePoints(ent, &muzzle);
    currentWeapon = ent->client->ps.currentWeapon;
    if (currentWeapon == 0) {
        priorityMap = bulletPriorityMap;
    } else {
        weaponInfo = (const weaponInfo_t *)BG_GetInfoForWeapon(currentWeapon);
        if (weaponInfo->ricochet == 0) {
            priorityMap = bulletPriorityMap;
        } else {
            priorityMap = riflePriorityMap;
        }
    }

    /* Stock 0x57d25..0x57d64: forward[k]*8192.0 kept 80-bit, + origin[k],
     * one store -> shim (mul+add). */
#if EMULATE_X87
    for (int k = 0; k < 3; k++) {
        end[k] = x87f_store_f32(x87f_add(
            x87f_mul(x87f_load_f32(muzzle.forward[k]), x87f_load_f32(8192.0f)),
            x87f_load_f32(muzzle.origin[k])));
    }
#else
    end[0] = muzzle.origin[0] + muzzle.forward[0] * 8192.0f;
    end[1] = muzzle.origin[1] + muzzle.forward[1] * 8192.0f;
    end[2] = muzzle.origin[2] + muzzle.forward[2] * 8192.0f;
#endif

    trap_LocationalTrace(&trace, muzzle.origin, end, ent->s_number,
                         PREVENT_FRIENDLY_FIRE_MASK, priorityMap);
    if (trace.entityNum >= ENTITYNUM_WORLD) {
        return;
    }

    trap_LocationalTrace(&trace, muzzle.origin, end, ent->s_number,
                         PREVENT_FRIENDLY_FIRE_CONFIRM_MASK, priorityMap);
    if (trace.entityNum < ENTITYNUM_WORLD) {
        gentity_t *hitEnt = &g_entities[trace.entityNum];

        if (hitEnt->scriptClassname == scr_const_trigger_friendlyfire) {
            ent->client->lookAtEntity = hitEnt;
            G_Trigger(hitEnt, ent);
        }
    }
}

/* ------------------------------------------------------------------ */
/*  0x435cc  G_PlayerEvent                                            */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x435cc, 535cc_G_PlayerEvent.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; recoil/FxOnTag event gate and BG_WeaponFireRecoil arguments match. */
void G_PlayerEvent(int entityNum, int event)
{
    vec3_t viewKick;
    gentity_t *ent;

    if ((event >= PLAYER_RECOIL_VIEWKICK_EVENT_MIN &&
         event <= PLAYER_RECOIL_VIEWKICK_EVENT_MAX) ||
        event == EV_FIRE_WEAPON_MG42) {
        ent = &g_entities[entityNum];
        BG_WeaponFireRecoil(ent->client, ent->client->fireRecoilVelocity,
                            viewKick);
    }
}

/* ------------------------------------------------------------------ */
/*  0x79f7a  G_AddPredictableEvent                                    */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x79f7a, 89f7a_G_AddPredictableEvent.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; client-gated predictable event append. */
void G_AddPredictableEvent(gentity_t *ent, int event, int eventParm)
{
    if (ent->client != NULL) {
        BG_AddPredictableEventToPlayerstate(event, eventParm, &ent->client->ps);
    }
}

/* ------------------------------------------------------------------ */
/*  0x79fbf  G_AddEvent                                               */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x79fbf, 89fbf_G_AddEvent.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; entity/client event rings and event-time stores via game_compat_g_set_entity_event_times helper. */
void G_AddEvent(gentity_t *ent, int event, int eventParm)
{
    int slot;

    if (ent->client == NULL) {
        slot = ent->eventCount & EVENT_RING_MASK;
        ent->events[slot] = event;
        ent->eventParms[slot] = eventParm;
        ent->eventCount = coduo_int32_from_bits(
            (uint32_t)ent->eventCount + UINT32_C(1));
    } else {
        slot = ent->client->ps.eventIndex & EVENT_RING_MASK;
        ent->client->ps.events[slot] = event;
        ent->client->ps.eventParms.ring[slot] = eventParm;
        ent->client->ps.eventIndex = coduo_int32_from_bits(
            (uint32_t)ent->client->ps.eventIndex + UINT32_C(1));
    }

    game_compat_g_set_entity_event_times(ent);
}

/* ------------------------------------------------------------------ */
/*  0x7a09d  G_PlaySoundAliasAtPoint                                  */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x7a09d, 8a09d_G_PlaySoundAliasAtPoint.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; nonzero sound alias temp entity. */
gentity_t *G_PlaySoundAliasAtPoint(const float *origin, uint8_t soundAlias)
{
    gentity_t *ent;

    if (soundAlias == 0) {
        return NULL;
    }

    ent = G_TempEntity(origin, EV_SOUND_ALIAS);
    ent->tempEffectId = soundAlias;
    return ent;
}

/* ------------------------------------------------------------------ */
/*  0x7a0f6  G_PlaySoundAlias                                         */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x7a0f6, 8a0f6_G_PlaySoundAlias.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; nonzero sound alias event. */
/*
 * Play a sound alias on an entity by adding a sound event.
 *
 * RECOVERED(UO-GAME-UNK-0176): Sound alias is added as event EV_SOUND_ALIAS (0xb1).
 */
void G_PlaySoundAlias(gentity_t *ent, uint8_t soundAlias)
{
    if (soundAlias != 0) {
        G_AddEvent(ent, EV_SOUND_ALIAS, soundAlias);
    }
}

/* ------------------------------------------------------------------ */
/*  0x7a135  G_AnimScriptSound                                        */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x7a135, 8a135_G_AnimScriptSound.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; entity lookup, sound alias index, and playback. */
void G_AnimScriptSound(int entityNum, const char *soundAliasName)
{
    gentity_t *ent = &g_entities[entityNum];

    G_PlaySoundAlias(ent, G_SoundAliasIndex(soundAliasName));
}

/* ------------------------------------------------------------------ */
/*  0x7a387 DebugLine                                                 */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x7a387, 8a387_DebugLine.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; constant zero return. */
int DebugLine(void)
{
    return 0;
}

/* ------------------------------------------------------------------ */
/* 0x77a8c G_LocalizedStringIndex */
/* VERIFIED_DECOMPILER(0x77a8c, 87a8c_G_LocalizedStringIndex.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; empty-string guard and localized configstring lookup. */
int G_LocalizedStringIndex(const char *value)
{
    if (value[0] == '\0') {
        return 0;
    }

    return G_FindConfigstringIndex(
        value,
        CS_LOCALIZED_STRINGS,
        CS_LOCALIZED_STRINGS_COUNT,
        level.spawning != 0,
        "localized string");
}

/* 0x77aed G_ShaderIndex */
/* VERIFIED_DECOMPILER(0x77aed, 87aed_G_ShaderIndex.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; shader configstring lookup; Ghidra's void inference drops the propagated return value. */
int G_ShaderIndex(const char *name)
{
    return G_FindConfigstringIndex(
        name,
        CS_SHADERS,
        CS_SHADERS_COUNT,
        level.spawning != 0,
        "shader");
}

/* 0x77c72 G_TagIndex */
/* VERIFIED_DECOMPILER(0x77c72, 87c72_G_TagIndex.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; tag configstring lookup with forced create; Ghidra's void inference drops the propagated return value. */
int G_TagIndex(const char *tagName)
{
    return G_FindConfigstringIndex(
        tagName,
        CS_TAGS,
        CS_TAGS_COUNT,
        qtrue,
        NULL);
}

/* 0x77cb5 G_EffectIndex */
/* VERIFIED_DECOMPILER(0x77cb5, 87cb5_G_EffectIndex.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; effect configstring lookup; Ghidra's void inference drops the propagated return value. */
int G_EffectIndex(const char *name)
{
    return G_FindConfigstringIndex(
        name,
        CS_EFFECTS,
        CS_EFFECTS_COUNT,
        level.spawning != 0,
        "effect");
}

/* 0x77cff G_ShellShockIndex */
/* VERIFIED_DECOMPILER(0x77cff, 87cff_G_ShellShockIndex.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; shellshock configstring lookup with forced create; Ghidra's void inference drops the propagated return value. */
int G_ShellShockIndex(const char *name)
{
    return G_FindConfigstringIndex(
        name,
        CS_SHELLSHOCKS,
        CS_SHELLSHOCKS_COUNT,
        qtrue,
        NULL);
}

/*  0x7a391 G_SetConstString                                          */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x7a391, 8a391_G_SetConstString.c, VERIFY-GAMEHELPERS-EVENTS-2026-06-17): DATAFLOW_VERIFIED; clear old string and assign SL_GetString result. */
void G_SetConstString(uint16_t *slot, const char *value)
{
    Scr_SetString(slot, 0);
    *slot = SL_GetString(value, 0);
}

/* ------------------------------------------------------------------ */
/*  0x7a3d7 G_BackupSpawnVars                                         */
/* ------------------------------------------------------------------ */

/* VERIFIED_DECOMPILER(0x7a3d7, 8a3d7_G_BackupSpawnVars.c, VERIFY-GAME-HELPERS-PARSE-INFO-2026-06-17): DATAFLOW_VERIFIED; hunk-copy spawn text and remap pair pointers. */
void G_BackupSpawnVars(gentity_t *ent)
{
    level_locals_t *lvl = &level;
    char *spawnText = game_compat_level_spawn_text(lvl);
    char **spawnVarPairs = game_compat_level_spawn_var_pairs(lvl);

    ent->savedSpawnText = trap_Hunk_AllocAlignInternal((size_t)lvl->spawnTextLength, 1);
    ent->savedSpawnTextLength = lvl->spawnTextLength;
    memcpy(ent->savedSpawnText, spawnText, (size_t)lvl->spawnTextLength);

    ent->savedSpawnVarPairs =
        trap_Hunk_AllocAlignInternal((size_t)lvl->spawnVarCount * 2u * sizeof(char *), 1);
    ent->savedSpawnVarCount = lvl->spawnVarCount;

    for (int pairIndex = 0; pairIndex < lvl->spawnVarCount; pairIndex++) {
        for (int slot = 0; slot < 2; slot++) {
            int index = pairIndex * 2 + slot;
            ent->savedSpawnVarPairs[index] =
                &ent->savedSpawnText[spawnVarPairs[index] - spawnText];
        }
    }
}
