#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>

#include "coduo_engine_structs.h"
#include "coduo_int32_bits.h"
#include "script_error_exception.hpp"

#define SCRIPT_INTERPRETER_FLOAT_EQUAL_EPSILON 1.0e-6f

enum script_interpreter_opcode_e {
    SCRIPT_OP_END = 0x00,
    SCRIPT_OP_RETURN = 0x01,
    SCRIPT_OP_GET_UNDEFINED = 0x02,
    SCRIPT_OP_GET_INTEGER = 0x03,
    SCRIPT_OP_GET_FLOAT = 0x04,
    SCRIPT_OP_GET_STRING = 0x05,
    SCRIPT_OP_GET_ISTRING = 0x06,
    SCRIPT_OP_GET_SELF_OBJECT = 0x07,
    SCRIPT_OP_GET_LEVEL_OBJECT = 0x08,
    SCRIPT_OP_GET_ANIM_OBJECT = 0x09,
    SCRIPT_OP_GET_SELF = 0x0a,
    SCRIPT_OP_GET_LEVEL = 0x0b,
    SCRIPT_OP_GET_GAME = 0x0c,
    SCRIPT_OP_GET_ANIM = 0x0d,
    SCRIPT_OP_GET_ANIMATION = 0x0e,
    SCRIPT_OP_GET_GAME_REF = 0x0f,
    SCRIPT_OP_GET_FUNCTION = 0x10,
    SCRIPT_OP_GET_LOCAL = 0x11,
    SCRIPT_OP_SET_LOCAL_REF = 0x12,
    SCRIPT_OP_CLEAR_LOCAL = 0x13,
    SCRIPT_OP_EVAL_INDEX = 0x14,
    SCRIPT_OP_SET_INDEXED_REF = 0x15,
    SCRIPT_OP_CLEAR_INDEXED = 0x16,
    SCRIPT_OP_NEW_ARRAY = 0x17,
    SCRIPT_OP_GET_FIELD = 0x18,
    SCRIPT_OP_SET_FIELD_REF = 0x19,
    SCRIPT_OP_CLEAR_FIELD = 0x1a,
    SCRIPT_OP_STORE_TEMP = 0x1b,
    SCRIPT_OP_SET_LOCAL = 0x1c,
    SCRIPT_OP_SET_LOCAL_AND_CLEAR = 0x1d,
    SCRIPT_OP_CLEAR_PARAMS_UNTIL_MARKER = 0x1e,
    SCRIPT_OP_FUNCTION_PARAMETERS_DONE = 0x1f,
    SCRIPT_OP_STORE_REF = 0x20,
    SCRIPT_OP_BUILTIN_FUNCTION = 0x21,
    SCRIPT_OP_BUILTIN_METHOD = 0x22,
    SCRIPT_OP_WAIT = 0x23,
    SCRIPT_OP_THREAD_MARKER = 0x24,
    SCRIPT_OP_CALL_FUNCTION = 0x25,
    SCRIPT_OP_CALL_POINTER = 0x26,
    SCRIPT_OP_CALL_FUNCTION_METHOD_CONTEXT = 0x27,
    SCRIPT_OP_CALL_POINTER_METHOD_CONTEXT = 0x28,
    SCRIPT_OP_THREAD_FUNCTION = 0x29,
    SCRIPT_OP_THREAD_POINTER = 0x2a,
    SCRIPT_OP_THREAD_FUNCTION_METHOD_CONTEXT = 0x2b,
    SCRIPT_OP_THREAD_POINTER_METHOD_CONTEXT = 0x2c,
    SCRIPT_OP_DROP_TOP = 0x2d,
    SCRIPT_OP_CAST_OBJECT = 0x2e,
    SCRIPT_OP_CAST_BOOL = 0x2f,
    SCRIPT_OP_CAST_INT = 0x30,
    SCRIPT_OP_CAST_FLOAT = 0x31,
    SCRIPT_OP_CAST_STRING = 0x32,
    SCRIPT_OP_BOOL_NOT = 0x33,
    SCRIPT_OP_BIT_NOT = 0x34,
    SCRIPT_OP_JUMP_ON_FALSE = 0x35,
    SCRIPT_OP_JUMP_ON_TRUE_BACK = 0x36,
    SCRIPT_OP_SHORT_CIRCUIT_AND = 0x37,
    SCRIPT_OP_SHORT_CIRCUIT_OR = 0x38,
    SCRIPT_OP_JUMP = 0x39,
    SCRIPT_OP_JUMP_BACK = 0x3a,
    SCRIPT_OP_INC = 0x3b,
    SCRIPT_OP_DEC = 0x3c,
    SCRIPT_OP_BIT_OR = 0x3d,
    SCRIPT_OP_BIT_XOR = 0x3e,
    SCRIPT_OP_BIT_AND = 0x3f,
    SCRIPT_OP_EQUAL = 0x40,
    SCRIPT_OP_NOT_EQUAL = 0x41,
    SCRIPT_OP_LESS = 0x42,
    SCRIPT_OP_GREATER = 0x43,
    SCRIPT_OP_LESS_EQUAL = 0x44,
    SCRIPT_OP_GREATER_EQUAL = 0x45,
    SCRIPT_OP_SHIFT_LEFT = 0x46,
    SCRIPT_OP_SHIFT_RIGHT = 0x47,
    SCRIPT_OP_PLUS = 0x48,
    SCRIPT_OP_MINUS = 0x49,
    SCRIPT_OP_MULTIPLY = 0x4a,
    SCRIPT_OP_DIVIDE = 0x4b,
    SCRIPT_OP_MODULO = 0x4c,
    SCRIPT_OP_SIZE = 0x4d,
    SCRIPT_OP_WAITTILLMATCH = 0x4e,
    SCRIPT_OP_WAITTILL = 0x4f,
    SCRIPT_OP_NOTIFY = 0x50,
    SCRIPT_OP_ENDON = 0x51,
    SCRIPT_OP_PUSH_CODEPOS = 0x52,
    SCRIPT_OP_SWITCH_JUMP = 0x53,
    SCRIPT_OP_SWITCH_TABLE = 0x54,
    SCRIPT_OP_VECTOR = 0x55,
    SCRIPT_OP_NOP = 0x56,
    SCRIPT_OP_DEVELOPER_COMMAND = 0x57,
    SCRIPT_OP_DEFERRED_DEVELOPER_CHECK = 0x58
};

enum {
    SCRIPT_INTERPRETER_MAX_CALL_DEPTH = CODUO_SCRIPT_CALL_STACK_COUNT,
    SCRIPT_INTERPRETER_TIME_MASK = 0x00ffffff,
    SCRIPT_INTERPRETER_MAX_WAIT_SECONDS = 16777,
    SCRIPT_INTERPRETER_WAIT_SCALE = 1000,
    SCRIPT_INTERPRETER_SHIFT_MASK = 0x1f,
    SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE = 0,
    SCRIPT_INTERPRETER_ENDON_NOTIFY_NAME = 0x20000,
    SCRIPT_INTERPRETER_NOTIFY_STACK_NAME = 0x20001,
    SCRIPT_INTERPRETER_CODEPOS_OPERAND_BYTES =
        sizeof(coduo_script_value_payload_t),
    SCRIPT_INTERPRETER_FUNCTION_CALL_OPERAND_BYTES =
        sizeof(coduo_script_value_payload_t) + sizeof(uint32_t),
    SCRIPT_INTERPRETER_POINTER_CALL_OPERAND_BYTES = sizeof(uint32_t),
    SCRIPT_INTERPRETER_SWITCH_CASE_ENTRY_BYTES =
        sizeof(uint32_t) + sizeof(coduo_script_value_payload_t)
};

typedef void (*script_builtin_function_t)(void);
typedef void (*script_builtin_method_t)(int32_t objectNum);

extern "C" {

extern uint8_t *script_codeBase;
extern uint8_t **script_developerOpcodePatchTable;
extern coduo_script_call_stack_codepos_t script_callStackCodepos;
extern uint32_t script_callStackDepth;
extern uint32_t script_currentTimeKey;
extern coduo_script_frame_backup_codepos_t script_frameBackupCodepos;
extern coduo_script_frame_backup_opcode_t script_frameBackupOpcode;
extern const char *script_errorMessage;
extern const char *script_errorSource;
extern int32_t script_errorParameterIndex;
extern uint8_t script_forceErrorReport;
extern uint32_t script_loopWatchdogTick;
extern int32_t
    script_loopWatchdogWarningFlag;
extern uint32_t script_parameterCount;
extern int32_t script_runtimeDeveloperFlag;
extern VariableValue *script_valueStackTop;
extern uint32_t script_valueStackDepth;
extern uint16_t script_animHandle;
extern uint16_t script_levelHandle;
extern uint16_t script_gameHandle;
extern uint16_t script_pauseArrayHandle;
extern uint16_t script_tempValueHandle;
extern uint16_t script_timeArrayHandle;
extern const char *script_variableTypeNames[CODUO_SCRIPT_VARIABLE_TYPE_COUNT];

extern void Com_Printf(const char *format, ...);
extern char *va(const char *format, ...);
extern void Scr_Error(const char *message);
extern void Scr_TerminalError(const char *message);
extern uint64_t rdtsc(void);
extern void ScriptRuntime_RaiseError(void);
extern void RuntimeError(coduo_script_codepos_t codePos,
                                               int32_t sourcePosOffset,
                                               const char *message,
                                               const char *detail);
extern void Scr_PrintPrevCodePos(int32_t channel,
                                coduo_script_codepos_t codePos,
                                int32_t sourcePosOffset);
extern void VM_ArchiveStack(
    int32_t valueCount, coduo_script_codepos_t codepos,
    uint16_t threadHandle,
    uint16_t objectHandle,
    VariableValue *valueBeforeArgs, uint32_t resumeTimeKey);
extern void VM_Notify(uint16_t objectHandle,
                      uint16_t notifyName,
                      VariableValue *argTop);
extern void KillThread(uint16_t objectHandle);
extern void SL_AddRefToString(uint16_t handle);
extern void SL_RemoveRefToString(uint16_t handle);
extern const char *SL_ConvertToString(uint16_t handle);
extern qboolean IsValidArrayIndex(uint32_t name);
extern uint32_t GetInternalVariableIndex(uint32_t name);
extern uint16_t
AllocThread(uint16_t parent);
extern void AddRefToObject(uint16_t handle);
extern void RemoveRefToObject(uint16_t handle);
extern uint16_t
GetSelf(uint16_t handle);
extern qboolean
IsFieldObject(uint16_t handle);
extern uint16_t
GetVariable(uint16_t parent, uint32_t name);
extern uint16_t
GetObjectVariable(uint16_t parent,
                              uint16_t child);
extern uint16_t
GetVariableField(uint16_t parent,
                             uint16_t name);
extern void SafeRemoveVariable(
    uint16_t parent, uint32_t name);
extern void ClearVariableField(uint16_t parent,
                                            uint16_t name);
extern void SetThreadNotifyName(uint16_t handle,
                                uint16_t name);
extern void ClearVariableValue(uint16_t handle);
extern void SetVariableValue(uint16_t handle,
                             VariableValue *value);
extern void SetVariableFieldValue(
    uint16_t handle, VariableValue *value);
extern void SetNewVariableValue(uint16_t handle,
                                VariableValue *value);
extern void GetVariableValue(uint16_t handle,
                                     VariableValue *value);
extern void GetVariableFieldValue(uint16_t handle,
                                           VariableValue *value);
extern uint16_t
GetObject(uint16_t handle);
extern uint16_t
GetArray(uint16_t handle);
extern coduo_script_variable_type_t
GetVarType(uint16_t handle);
extern uint16_t
GetArraySize(uint16_t handle);
extern float *AllocVector(void);
extern void RemoveRefToVector(coduo_script_value_payload_t vectorPayload);
extern uint16_t
CastFieldObject(VariableValue *value);
extern void EvalArray(VariableValue *index, VariableValue *container);
extern uint16_t EvalArrayRef(uint16_t handle, VariableValue *index);
extern void ClearArray(uint16_t handle,
                       VariableValue *index);
extern void GetEmptyArray(VariableValue *value);
extern void ScriptValue_AddRefValue(VariableValue *value);
extern void ScriptValue_ReleaseValue(VariableValue *value);
extern qboolean CastBool(VariableValue *value);
extern qboolean CastInt(VariableValue *value);
extern qboolean CastFloat(VariableValue *value);
extern qboolean CastString(VariableValue *value);
extern void CastVector2(VariableValue values[3]);
extern void ClearVector(VariableValue values[3]);
extern void UnmatchingTypesError(VariableValue *left,
                                 VariableValue *right);
extern qboolean CheckEquality(VariableValue *left,
                              VariableValue *right);
extern qboolean CastWeakerPair(VariableValue *left,
                              VariableValue *right);
extern uint16_t VM_ConcatenateStrings(VariableValue *values);
extern int32_t FastRound(float value);
extern long double FUN_080b085c(float value);
extern void GetSizeValue(VariableValue *value);
extern uint16_t
VM_Execute(VariableValue *stackTop, uint8_t *codePos,
           uint16_t thread,
           uint16_t currentObject,
           VariableValue *stackBase);

}

qboolean CastWeakerPair(VariableValue values[2])
{
    return ::CastWeakerPair(&values[0], &values[1]);
}

/* NOT_FROM_ORIGINAL_SOURCE: non-x86 compatibility for the stock IDIV
 * quotient-overflow exception.  x86 targets retain their direct IDIV path. */
static int32_t coduomp_script_divide_i32(int32_t dividend, int32_t divisor)
{
#if !defined(__i386__) && !defined(__x86_64__)
    if (dividend == INT32_MIN && divisor == -1) {
        raise(SIGFPE);
    }
#endif
    return dividend / divisor;
}

/* NOT_FROM_ORIGINAL_SOURCE: non-x86 compatibility for the stock IDIV
 * quotient-overflow exception used by the remainder opcode. */
static int32_t coduomp_script_remainder_i32(int32_t dividend,
                                           int32_t divisor)
{
#if !defined(__i386__) && !defined(__x86_64__)
    if (dividend == INT32_MIN && divisor == -1) {
        raise(SIGFPE);
    }
#endif
    return dividend % divisor;
}

/* NOT_FROM_ORIGINAL_SOURCE: bytecode reader for packed script VM operands. */
static uint16_t ScriptInterpreter_ReadU16(const uint8_t *codePos)
{
    uint16_t value;
    memcpy(&value, codePos, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: bytecode reader for packed script VM operands. */
static int16_t ScriptInterpreter_ReadI16(const uint8_t *codePos)
{
    int16_t value;
    memcpy(&value, codePos, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: bytecode reader for packed script VM operands. */
static int32_t ScriptInterpreter_ReadI32(const uint8_t *codePos)
{
    int32_t value;
    memcpy(&value, codePos, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: bytecode reader for packed script VM operands. */
static uint32_t ScriptInterpreter_ReadU32(const uint8_t *codePos)
{
    uint32_t value;
    memcpy(&value, codePos, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: bytecode reader for host-width script payloads. */
static coduo_script_value_payload_t
ScriptInterpreter_ReadValuePayload(const uint8_t *codePos)
{
    coduo_script_value_payload_t value;
    memcpy(&value, codePos, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: converts script code payloads at VM ABI edges. */
static coduo_script_value_payload_t
ScriptInterpreter_CodeposPayload(const uint8_t *codePos)
{
    return (coduo_script_value_payload_t)codePos;
}

/* NOT_FROM_ORIGINAL_SOURCE: converts script code payloads at VM ABI edges. */
static uint8_t *
ScriptInterpreter_CodeposFromPayload(coduo_script_value_payload_t payload)
{
    return (uint8_t *)payload;
}

/* NOT_FROM_ORIGINAL_SOURCE: bytecode reader for packed callback pointers. */
static script_builtin_function_t
ScriptInterpreter_ReadBuiltinFunction(const uint8_t *codePos)
{
    script_builtin_function_t value;
    memcpy(&value, codePos, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: bytecode reader for packed callback pointers. */
static script_builtin_method_t
ScriptInterpreter_ReadBuiltinMethod(const uint8_t *codePos)
{
    script_builtin_method_t value;
    memcpy(&value, codePos, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves script value float payload encoding. */
static coduo_script_value_payload_t ScriptInterpreter_FloatPayload(float value)
{
    coduo_script_value_payload_t payload = 0;

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

/* NOT_FROM_ORIGINAL_SOURCE: preserves script value float payload encoding. */
static float ScriptInterpreter_PayloadFloat(coduo_script_value_payload_t payload)
{
    float value;
    memcpy(&value, &payload, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: helper for script VM stack pushes. */
static VariableValue *
ScriptInterpreter_Push(VariableValue *stackTop,
                       coduo_script_variable_type_t type,
                       coduo_script_value_payload_t payload)
{
    ++stackTop;
    stackTop->payload = payload;
    stackTop->type = type;
    return stackTop;
}

/* NOT_FROM_ORIGINAL_SOURCE: typed access to script vector payload storage. */
static float *ScriptInterpreter_Vector(coduo_script_value_payload_t payload)
{
    return (float *)payload;
}

/* NOT_FROM_ORIGINAL_SOURCE: shared call-depth guard for bytecode dispatch. */
static void ScriptInterpreter_CheckCallDepth(void)
{
    if (script_callStackDepth >= SCRIPT_INTERPRETER_MAX_CALL_DEPTH) {
        script_errorParameterIndex = 1;
        Scr_Error("script stack overflow (too many embedded function calls)");
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: shared live-object validator for VM dispatch. */
static uint16_t
ScriptInterpreter_RequireObject(VariableValue *value,
                                int32_t parameterIndex)
{
    if (value->type != CODUO_SCRIPT_VAR_OBJECT) {
        script_errorParameterIndex = parameterIndex;
        Scr_Error(
            va("%s is not an object", script_variableTypeNames[value->type]));
    }

    uint16_t object =
        (uint16_t)value->payload;
    if (IsFieldObject(object) == qfalse) {
        script_errorParameterIndex = parameterIndex;
        coduo_script_variable_type_t type = GetVarType(object);
        Scr_Error(
            va("%s is not an object", script_variableTypeNames[type]));
    }

    return object;
}

/* NOT_FROM_ORIGINAL_SOURCE: installs a nested script-call frame. */
static void ScriptInterpreter_SaveCallCodepos(uint8_t *codePos)
{
    script_callStackCodepos[script_callStackDepth] = codePos;
    script_callStackDepth++;
}

/* NOT_FROM_ORIGINAL_SOURCE: prepares a child script function call frame. */
static void ScriptInterpreter_EnterFunctionCall(
    VariableValue **stackTop, uint8_t **codePos,
    uint16_t *thread,
    uint16_t parent, int32_t argumentCount,
    uint8_t *targetCodePos, uint8_t *returnCodePos,
    qboolean addParentRef)
{
    ScriptInterpreter_CheckCallDepth();
    ScriptInterpreter_SaveCallCodepos(*codePos);

    if (addParentRef != qfalse) {
        AddRefToObject(parent);
    }
    *thread = AllocThread(parent);

    VariableValue *marker = *stackTop - argumentCount;
    marker->payload = ScriptInterpreter_CodeposPayload(returnCodePos);
    marker->type = CODUO_SCRIPT_VAR_CODEPOS;
    *codePos = targetCodePos;
}

/* NOT_FROM_ORIGINAL_SOURCE: runs a bytecode thread call and releases it. */
static VariableValue *ScriptInterpreter_RunThreadCall(
    VariableValue *callTop, uint8_t *targetCodePos,
    uint16_t threadParent,
    VariableValue *marker)
{
    coduo_script_variable_type_t savedType = marker->type;

    marker->type = CODUO_SCRIPT_VAR_CODEPOS;
    uint16_t thread =
        AllocThread(threadParent);
    uint16_t result =
        VM_Execute(callTop, targetCodePos, thread, thread, marker);
    RemoveRefToObject(result);
    marker->type = savedType;

    return marker + 1;
}

/* NOT_FROM_ORIGINAL_SOURCE: helper for script VM stack cleanups. */
static void ScriptInterpreter_ReleaseActiveParams(void)
{
    while (script_parameterCount != 0) {
        ScriptValue_ReleaseValue(script_valueStackTop);
        --script_valueStackTop;
        --script_parameterCount;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: common return-through-call-frame cleanup. */
static qboolean ScriptInterpreter_ReturnFromFrame(
    VariableValue **stackTop, uint8_t **codePos,
    uint16_t *thread,
    VariableValue *stackBase, VariableValue *returnValue)
{
    VariableValue *top = *stackTop;

    while (top->type != CODUO_SCRIPT_VAR_CODEPOS) {
        ScriptValue_ReleaseValue(top);
        --top;
    }

    --script_callStackDepth;
    if (top == stackBase) {
        stackBase[1] = *returnValue;
        *stackTop = top;
        return qtrue;
    }

    RemoveRefToObject(*thread);
    *codePos = (uint8_t *)top->payload;
    --top;
    *thread = (uint16_t)top->payload;
    *top = *returnValue;

    uint16_t parent =
        GetSelf(*thread);
    if (IsFieldObject(parent) == qfalse) {
        while (true) {
            KillThread(*thread);
            while (top->type != CODUO_SCRIPT_VAR_CODEPOS) {
                ScriptValue_ReleaseValue(top);
                --top;
            }
            --script_callStackDepth;
            if (top == stackBase) {
                stackBase[1].payload = SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE;
                stackBase[1].type = CODUO_SCRIPT_VAR_UNDEFINED;
                *stackTop = top;
                return qtrue;
            }

            RemoveRefToObject(*thread);
            --top;
            *thread = (uint16_t)top->payload;
            top->type = CODUO_SCRIPT_VAR_UNDEFINED;
        }
    }

    *stackTop = top;
    return qfalse;
}

/* NOT_FROM_ORIGINAL_SOURCE: restores after a script error throw. */
static void ScriptInterpreter_CleanupAfterError(
    uint8_t opcode, VariableValue **stackTop, uint8_t **codePos,
    uint16_t *fieldRef, int32_t switchCaseCount)
{
    switch ((script_interpreter_opcode_e)opcode) {
    case SCRIPT_OP_EVAL_INDEX:
        ScriptValue_ReleaseValue(*stackTop);
        ScriptValue_ReleaseValue(*stackTop + 1);
        (*stackTop)->type = CODUO_SCRIPT_VAR_UNDEFINED;
        break;

    case SCRIPT_OP_SET_INDEXED_REF:
        ClearVariableValue(script_tempValueHandle);
        *fieldRef = script_tempValueHandle;
        /* Fall through. */

    case SCRIPT_OP_CLEAR_INDEXED:
        if (script_errorParameterIndex < 0) {
            script_errorParameterIndex = 1;
        }
        ScriptValue_ReleaseValue(*stackTop);
        *stackTop -= 1;
        break;

    case SCRIPT_OP_GET_FIELD:
    case SCRIPT_OP_CLEAR_FIELD:
        script_errorParameterIndex = 0;
        *codePos += sizeof(uint16_t);
        break;

    case SCRIPT_OP_SET_LOCAL:
    case SCRIPT_OP_SET_LOCAL_AND_CLEAR:
    case SCRIPT_OP_STORE_REF:
        ScriptInterpreter_ReleaseActiveParams();
        script_errorParameterIndex = 0;
        *stackTop -= 1;
        if (opcode == SCRIPT_OP_SET_LOCAL ||
            opcode == SCRIPT_OP_SET_LOCAL_AND_CLEAR) {
            *codePos += sizeof(uint16_t);
        }
        break;

    case SCRIPT_OP_FUNCTION_PARAMETERS_DONE:
        while ((*stackTop)->type != CODUO_SCRIPT_VAR_CODEPOS) {
            ScriptValue_ReleaseValue(*stackTop);
            *stackTop -= 1;
        }
        break;

    case SCRIPT_OP_BUILTIN_FUNCTION:
        if (script_errorParameterIndex > 0) {
            script_errorParameterIndex =
                (script_parameterCount - script_errorParameterIndex) + 1;
        }
        *codePos += sizeof(uint8_t) + sizeof(script_builtin_function_t);
        ScriptInterpreter_ReleaseActiveParams();
        *stackTop = ScriptInterpreter_Push(
            script_valueStackTop, CODUO_SCRIPT_VAR_UNDEFINED,
            SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE);
        break;

    case SCRIPT_OP_BUILTIN_METHOD:
        if (script_errorParameterIndex < 0) {
            script_errorParameterIndex = 1;
        } else if (script_errorParameterIndex > 0) {
            script_errorParameterIndex =
                (script_parameterCount - script_errorParameterIndex) + 2;
        }
        *codePos += sizeof(uint8_t) + sizeof(script_builtin_method_t);
        ScriptInterpreter_ReleaseActiveParams();
        *stackTop = ScriptInterpreter_Push(
            script_valueStackTop, CODUO_SCRIPT_VAR_UNDEFINED,
            SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE);
        break;

    case SCRIPT_OP_WAIT:
        script_errorParameterIndex = 1;
        *stackTop -= 1;
        break;

    case SCRIPT_OP_CALL_FUNCTION:
    case SCRIPT_OP_CALL_FUNCTION_METHOD_CONTEXT: {
        int32_t argumentCount =
            ScriptInterpreter_ReadI32(*codePos +
                                      SCRIPT_INTERPRETER_CODEPOS_OPERAND_BYTES);
        for (int32_t index = 0; index < argumentCount; ++index) {
            ScriptValue_ReleaseValue(*stackTop);
            *stackTop -= 1;
        }
        if (opcode == SCRIPT_OP_CALL_FUNCTION_METHOD_CONTEXT) {
            ScriptValue_ReleaseValue(*stackTop);
            *stackTop -= 1;
        }
        (*stackTop)->type = CODUO_SCRIPT_VAR_UNDEFINED;
        *codePos += SCRIPT_INTERPRETER_FUNCTION_CALL_OPERAND_BYTES;
        break;
    }

    case SCRIPT_OP_CALL_POINTER:
    case SCRIPT_OP_CALL_POINTER_METHOD_CONTEXT:
    case SCRIPT_OP_THREAD_POINTER:
    case SCRIPT_OP_THREAD_POINTER_METHOD_CONTEXT: {
        int32_t argumentCount = ScriptInterpreter_ReadI32(*codePos);
        int32_t valueCount = argumentCount + 1;
        for (int32_t index = 0; index < valueCount; ++index) {
            ScriptValue_ReleaseValue(*stackTop);
            *stackTop -= 1;
        }
        *stackTop = ScriptInterpreter_Push(
            *stackTop, CODUO_SCRIPT_VAR_UNDEFINED,
            SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE);
        *codePos += SCRIPT_INTERPRETER_POINTER_CALL_OPERAND_BYTES;
        break;
    }

    case SCRIPT_OP_THREAD_FUNCTION_METHOD_CONTEXT: {
        int32_t argumentCount =
            ScriptInterpreter_ReadI32(*codePos +
                                      SCRIPT_INTERPRETER_CODEPOS_OPERAND_BYTES);
        int32_t valueCount = argumentCount + 1;
        for (int32_t index = 0; index < valueCount; ++index) {
            ScriptValue_ReleaseValue(*stackTop);
            *stackTop -= 1;
        }
        *stackTop = ScriptInterpreter_Push(
            *stackTop, CODUO_SCRIPT_VAR_UNDEFINED,
            SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE);
        *codePos += SCRIPT_INTERPRETER_FUNCTION_CALL_OPERAND_BYTES;
        break;
    }

    case SCRIPT_OP_CAST_OBJECT:
        ScriptValue_ReleaseValue(*stackTop);
        ClearVariableValue(script_tempValueHandle);
        *fieldRef = GetObject(script_tempValueHandle);
        *stackTop -= 1;
        break;

    case SCRIPT_OP_BIT_NOT:
    case SCRIPT_OP_SIZE:
        ScriptValue_ReleaseValue(*stackTop);
        (*stackTop)->type = CODUO_SCRIPT_VAR_UNDEFINED;
        break;

    case SCRIPT_OP_JUMP_ON_FALSE:
    case SCRIPT_OP_JUMP_ON_TRUE_BACK:
    case SCRIPT_OP_SHORT_CIRCUIT_AND:
    case SCRIPT_OP_SHORT_CIRCUIT_OR:
        *stackTop -= 1;
        *codePos += sizeof(int32_t);
        break;

    case SCRIPT_OP_INC:
    case SCRIPT_OP_DEC:
        script_errorParameterIndex = 0;
        break;

    case SCRIPT_OP_WAITTILL:
    case SCRIPT_OP_WAITTILLMATCH:
    case SCRIPT_OP_ENDON:
        if (opcode == SCRIPT_OP_WAITTILLMATCH) {
            *codePos += sizeof(uint8_t);
        }
        ScriptValue_ReleaseValue(*stackTop);
        ScriptValue_ReleaseValue(*stackTop - 1);
        *stackTop -= 2;
        break;

    case SCRIPT_OP_NOTIFY:
        while ((*stackTop)->type != CODUO_SCRIPT_VAR_CODEPOS) {
            ScriptValue_ReleaseValue(*stackTop);
            *stackTop -= 1;
        }
        ScriptValue_ReleaseValue(*stackTop);
        *stackTop -= 1;
        break;

    case SCRIPT_OP_SWITCH_JUMP:
        ScriptValue_ReleaseValue(*stackTop);
        *stackTop -= 1;
        (void)switchCaseCount;
        break;

    case SCRIPT_OP_VECTOR:
        ClearVector(*stackTop - 2);
        *stackTop -= 2;
        break;

    default:
        break;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: reports a recovered C++ script exception. */
static void ScriptInterpreter_ReportAndContinue(uint8_t *codePos)
{
    RuntimeError(
        codePos, script_errorParameterIndex, script_errorMessage,
        script_errorSource);
    script_errorMessage = NULL;
    script_errorSource = NULL;
    script_errorParameterIndex = 0;
}

extern "C" uint16_t
VM_Execute(VariableValue *stackTop, uint8_t *codePos,
           uint16_t thread,
           uint16_t currentObject,
           VariableValue *stackBase)
{
    uint16_t fieldRef = 0;
    int32_t developerDepth = 0;
    int32_t switchCaseCount = 0;
    uint8_t opcode = SCRIPT_OP_NOP;

    for (;;) {
        try {
            opcode = *codePos++;

            switch ((script_interpreter_opcode_e)opcode) {
            case SCRIPT_OP_END: {
                KillThread(thread);
                VariableValue returnValue;
                returnValue.payload = SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE;
                returnValue.type = CODUO_SCRIPT_VAR_UNDEFINED;
                if (ScriptInterpreter_ReturnFromFrame(
                        &stackTop, &codePos, &thread, stackBase,
                        &returnValue) != qfalse) {
                    return thread;
                }
                break;
            }

            case SCRIPT_OP_RETURN: {
                KillThread(thread);
                VariableValue returnValue = *stackTop;
                --stackTop;
                if (ScriptInterpreter_ReturnFromFrame(
                        &stackTop, &codePos, &thread, stackBase,
                        &returnValue) != qfalse) {
                    return thread;
                }
                break;
            }

            case SCRIPT_OP_GET_UNDEFINED:
                stackTop = ScriptInterpreter_Push(
                    stackTop, CODUO_SCRIPT_VAR_UNDEFINED,
                    SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE);
                break;

            case SCRIPT_OP_GET_INTEGER:
                stackTop = ScriptInterpreter_Push(
                    stackTop, CODUO_SCRIPT_VAR_INT,
                    ScriptInterpreter_ReadU32(codePos));
                codePos += sizeof(uint32_t);
                break;

            case SCRIPT_OP_GET_FLOAT:
                stackTop = ScriptInterpreter_Push(
                    stackTop, CODUO_SCRIPT_VAR_FLOAT,
                    ScriptInterpreter_ReadU32(codePos));
                codePos += sizeof(uint32_t);
                break;

            case SCRIPT_OP_GET_STRING:
            case SCRIPT_OP_GET_ISTRING: {
                uint16_t string =
                    ScriptInterpreter_ReadU16(codePos);
                codePos += sizeof(uint16_t);
                SL_AddRefToString(string);
                stackTop = ScriptInterpreter_Push(
                    stackTop,
                    opcode == SCRIPT_OP_GET_STRING
                        ? CODUO_SCRIPT_VAR_STRING
                        : CODUO_SCRIPT_VAR_LOCALIZED_STRING,
                    string);
                break;
            }

            case SCRIPT_OP_GET_SELF_OBJECT:
                fieldRef = GetSelf(thread);
                break;

            case SCRIPT_OP_GET_LEVEL_OBJECT:
                fieldRef = script_levelHandle;
                break;

            case SCRIPT_OP_GET_ANIM_OBJECT:
                fieldRef = script_animHandle;
                break;

            case SCRIPT_OP_GET_SELF: {
                uint16_t parent =
                    GetSelf(thread);
                AddRefToObject(parent);
                stackTop =
                    ScriptInterpreter_Push(stackTop, CODUO_SCRIPT_VAR_OBJECT,
                                           parent);
                break;
            }

            case SCRIPT_OP_GET_LEVEL:
                AddRefToObject(script_levelHandle);
                stackTop = ScriptInterpreter_Push(
                    stackTop, CODUO_SCRIPT_VAR_OBJECT, script_levelHandle);
                break;

            case SCRIPT_OP_GET_GAME: {
                VariableValue value;
                GetVariableValue(script_gameHandle, &value);
                ++stackTop;
                *stackTop = value;
                ScriptValue_AddRefValue(stackTop);
                break;
            }

            case SCRIPT_OP_GET_ANIM:
                AddRefToObject(script_animHandle);
                stackTop = ScriptInterpreter_Push(stackTop,
                                                  CODUO_SCRIPT_VAR_OBJECT,
                                                  script_animHandle);
                break;

            case SCRIPT_OP_GET_ANIMATION:
                stackTop = ScriptInterpreter_Push(
                    stackTop, CODUO_SCRIPT_VAR_ANIMATION,
                    ScriptInterpreter_ReadU32(codePos));
                codePos += sizeof(uint32_t);
                break;

            case SCRIPT_OP_GET_GAME_REF:
                fieldRef = script_gameHandle;
                break;

            case SCRIPT_OP_GET_FUNCTION:
                stackTop = ScriptInterpreter_Push(
                    stackTop, CODUO_SCRIPT_VAR_FUNCTION,
                    ScriptInterpreter_ReadValuePayload(codePos));
                codePos += SCRIPT_INTERPRETER_CODEPOS_OPERAND_BYTES;
                break;

            case SCRIPT_OP_GET_LOCAL: {
                uint16_t child =
                    GetVariable(thread,
                                            ScriptInterpreter_ReadU16(codePos));
                codePos += sizeof(uint16_t);
                ++stackTop;
                GetVariableValue(child, stackTop);
                ScriptValue_AddRefValue(stackTop);
                break;
            }

            case SCRIPT_OP_SET_LOCAL_REF:
                fieldRef = GetVariable(
                    thread, ScriptInterpreter_ReadU16(codePos));
                codePos += sizeof(uint16_t);
                break;

            case SCRIPT_OP_CLEAR_LOCAL:
                SafeRemoveVariable(
                    thread, ScriptInterpreter_ReadU16(codePos));
                codePos += sizeof(uint16_t);
                break;

            case SCRIPT_OP_EVAL_INDEX:
                --stackTop;
                EvalArray(stackTop, stackTop + 1);
                break;

            case SCRIPT_OP_SET_INDEXED_REF:
                fieldRef = EvalArrayRef(fieldRef, stackTop);
                --stackTop;
                break;

            case SCRIPT_OP_CLEAR_INDEXED:
                ClearArray(fieldRef, stackTop);
                --stackTop;
                break;

            case SCRIPT_OP_NEW_ARRAY:
                ++stackTop;
                GetEmptyArray(stackTop);
                break;

            case SCRIPT_OP_GET_FIELD: {
                uint16_t child =
                    GetVariableField(
                        fieldRef, ScriptInterpreter_ReadU16(codePos));
                codePos += sizeof(uint16_t);
                ++stackTop;
                GetVariableFieldValue(child, stackTop);
                break;
            }

            case SCRIPT_OP_SET_FIELD_REF:
                fieldRef = GetVariableField(
                    fieldRef, ScriptInterpreter_ReadU16(codePos));
                codePos += sizeof(uint16_t);
                break;

            case SCRIPT_OP_CLEAR_FIELD:
                ClearVariableField(
                    fieldRef, ScriptInterpreter_ReadU16(codePos));
                codePos += sizeof(uint16_t);
                break;

            case SCRIPT_OP_STORE_TEMP:
                SetVariableValue(script_tempValueHandle, stackTop);
                fieldRef = script_tempValueHandle;
                --stackTop;
                break;

            case SCRIPT_OP_SET_LOCAL:
            case SCRIPT_OP_SET_LOCAL_AND_CLEAR:
                if (stackTop->type == CODUO_SCRIPT_VAR_CODEPOS) {
                    if (opcode == SCRIPT_OP_SET_LOCAL_AND_CLEAR) {
                        SafeRemoveVariable(
                            thread, ScriptInterpreter_ReadU16(codePos));
                    }
                } else {
                    uint16_t child =
                        GetVariable(
                            thread, ScriptInterpreter_ReadU16(codePos));
                    SetVariableFieldValue(child, stackTop);
                    --stackTop;
                }
                codePos += sizeof(uint16_t);
                break;

            case SCRIPT_OP_CLEAR_PARAMS_UNTIL_MARKER:
                while (stackTop->type != CODUO_SCRIPT_VAR_CODEPOS) {
                    ScriptValue_ReleaseValue(stackTop);
                    --stackTop;
                }
                break;

            case SCRIPT_OP_FUNCTION_PARAMETERS_DONE:
                if (stackTop->type != CODUO_SCRIPT_VAR_CODEPOS) {
                    Scr_Error("function called with too many parameters");
                }
                break;

            case SCRIPT_OP_STORE_REF:
                SetVariableFieldValue(fieldRef, stackTop);
                --stackTop;
                break;

            case SCRIPT_OP_BUILTIN_FUNCTION: {
                script_parameterCount = *codePos;
                script_valueStackTop = stackTop;
                script_builtin_function_t callback =
                    ScriptInterpreter_ReadBuiltinFunction(codePos + 1);
                callback();
                stackTop = script_valueStackTop;
                ScriptInterpreter_ReleaseActiveParams();
                stackTop = script_valueStackTop;
                codePos += sizeof(uint8_t) + sizeof(callback);
                if (script_valueStackDepth == 0) {
                    stackTop++;
                    stackTop->type = CODUO_SCRIPT_VAR_UNDEFINED;
                } else {
                    script_valueStackDepth = 0;
                }
                break;
            }

            case SCRIPT_OP_BUILTIN_METHOD: {
                script_parameterCount = *codePos;
                script_valueStackTop = stackTop - 1;
                if (stackTop->type != CODUO_SCRIPT_VAR_OBJECT) {
                    coduo_script_variable_type_t type = stackTop->type;
                    ScriptValue_ReleaseValue(stackTop);
                    script_errorParameterIndex = -1;
                    Scr_Error(va("%s is not an entity",
                                          script_variableTypeNames[type]));
                }

                uint16_t object =
                    (uint16_t)stackTop->payload;
                coduo_script_variable_type_t type = GetVarType(object);
                if (type != CODUO_SCRIPT_VAR_ENTITY) {
                    RemoveRefToObject(object);
                    script_errorParameterIndex = -1;
                    Scr_Error(
                        va("%s is not an entity", script_variableTypeNames[type]));
                }

                int32_t objectNum =
                    (int32_t)GetSelf(object);
                RemoveRefToObject(object);
                script_builtin_method_t callback =
                    ScriptInterpreter_ReadBuiltinMethod(codePos + 1);
                callback(objectNum);
                stackTop = script_valueStackTop;
                ScriptInterpreter_ReleaseActiveParams();
                stackTop = script_valueStackTop;
                codePos += sizeof(uint8_t) + sizeof(callback);
                if (script_valueStackDepth == 0) {
                    stackTop++;
                    stackTop->type = CODUO_SCRIPT_VAR_UNDEFINED;
                } else {
                    script_valueStackDepth = 0;
                }
                break;
            }

            case SCRIPT_OP_WAIT:
                if (developerDepth != 0) {
                    Scr_Error(
                        "wait not allowed in /# ... #/ comment (call as a thread to fix)");
                }
                if (CastFloat(stackTop) == qfalse) {
                    ScriptRuntime_RaiseError();
                } else {
                    float waitSeconds =
                        ScriptInterpreter_PayloadFloat(stackTop->payload);
                    if (waitSeconds < 0.0f) {
                        Scr_Error(
                            va("negative wait of %g is not allowed",
                               (double)waitSeconds));
                    } else if (waitSeconds >=
                        (float)SCRIPT_INTERPRETER_MAX_WAIT_SECONDS) {
                        Scr_Error(
                            va("wait of %.0f seconds is too long",
                               (double)waitSeconds));
                        Scr_Error(
                            va("negative wait of %g is not allowed",
                               (double)waitSeconds));
                    } else {
                        int32_t waitMsec = FastRound(
                            waitSeconds * SCRIPT_INTERPRETER_WAIT_SCALE);
                        uint32_t resumeTime =
                            (script_currentTimeKey + (uint32_t)waitMsec) &
                            SCRIPT_INTERPRETER_TIME_MASK;
                        uint16_t timeSlot =
                            GetVariable(script_timeArrayHandle,
                                                    resumeTime);
                        uint16_t timeArray =
                            GetArray(timeSlot);
                        GetObjectVariable(timeArray,
                                                      currentObject);
                        VM_ArchiveStack(
                            (int32_t)(stackTop - stackBase - 1),
                            codePos, thread, currentObject, stackBase,
                            resumeTime);
                        stackBase[1].type = CODUO_SCRIPT_VAR_UNDEFINED;
                        return currentObject;
                    }
                }
                /* fall through */

            case SCRIPT_OP_THREAD_MARKER:
                AddRefToObject(thread);
                stackTop = ScriptInterpreter_Push(stackTop,
                                                  CODUO_SCRIPT_VAR_OBJECT,
                                                  thread);
                stackTop++;
                stackTop->type = CODUO_SCRIPT_VAR_UNDEFINED;
                break;

            case SCRIPT_OP_CALL_FUNCTION:
            case SCRIPT_OP_CALL_FUNCTION_METHOD_CONTEXT: {
                uint8_t *targetCodePos = ScriptInterpreter_CodeposFromPayload(
                    ScriptInterpreter_ReadValuePayload(codePos));
                int32_t argumentCount =
                    ScriptInterpreter_ReadI32(
                        codePos + SCRIPT_INTERPRETER_CODEPOS_OPERAND_BYTES);
                uint8_t *returnCodePos =
                    codePos + SCRIPT_INTERPRETER_FUNCTION_CALL_OPERAND_BYTES;
                uint16_t parent;

                RemoveRefToObject(thread);
                if (opcode == SCRIPT_OP_CALL_FUNCTION) {
                    parent = GetSelf(thread);
                } else {
                    uint16_t object =
                        ScriptInterpreter_RequireObject(stackTop, 1);
                    parent = object;
                    --stackTop;
                }

                ScriptInterpreter_EnterFunctionCall(
                    &stackTop, &codePos, &thread, parent, argumentCount,
                    targetCodePos, returnCodePos,
                    opcode == SCRIPT_OP_CALL_FUNCTION ? qtrue : qfalse);
                break;
            }

            case SCRIPT_OP_CALL_POINTER:
            case SCRIPT_OP_CALL_POINTER_METHOD_CONTEXT: {
                int32_t argumentCount = ScriptInterpreter_ReadI32(codePos);

                RemoveRefToObject(thread);
                if (stackTop->type != CODUO_SCRIPT_VAR_FUNCTION) {
                    coduo_script_variable_type_t badType =
                        (coduo_script_variable_type_t)stackTop->type;
                    if (opcode == SCRIPT_OP_CALL_POINTER_METHOD_CONTEXT) {
                        ScriptValue_ReleaseValue(stackTop);
                        --stackTop;
                    }
                    Scr_Error(
                        va("%s is not a function pointer",
                           script_variableTypeNames[badType]));
                }

                uint8_t *targetCodePos =
                    ScriptInterpreter_CodeposFromPayload(stackTop->payload);
                --stackTop;

                uint16_t parent;
                if (opcode == SCRIPT_OP_CALL_POINTER) {
                    parent = GetSelf(thread);
                } else {
                    uint16_t object =
                        ScriptInterpreter_RequireObject(stackTop, 2);
                    parent = object;
                    --stackTop;
                }

                ScriptInterpreter_EnterFunctionCall(
                    &stackTop, &codePos, &thread, parent, argumentCount,
                    targetCodePos,
                    codePos + SCRIPT_INTERPRETER_POINTER_CALL_OPERAND_BYTES,
                    opcode == SCRIPT_OP_CALL_POINTER ? qtrue : qfalse);
                break;
            }

            case SCRIPT_OP_THREAD_FUNCTION:
            case SCRIPT_OP_THREAD_FUNCTION_METHOD_CONTEXT: {
                uint8_t *targetCodePos = ScriptInterpreter_CodeposFromPayload(
                    ScriptInterpreter_ReadValuePayload(codePos));
                int32_t argumentCount =
                    ScriptInterpreter_ReadI32(
                        codePos + SCRIPT_INTERPRETER_CODEPOS_OPERAND_BYTES);
                VariableValue *marker;
                uint16_t parent;

                if (opcode == SCRIPT_OP_THREAD_FUNCTION) {
                    ScriptInterpreter_CheckCallDepth();
                    ScriptInterpreter_SaveCallCodepos(codePos);
                    parent = GetSelf(thread);
                    AddRefToObject(parent);
                    marker = stackTop - argumentCount;
                    stackTop = ScriptInterpreter_RunThreadCall(
                        stackTop, targetCodePos, parent, marker);
                } else {
                    parent = ScriptInterpreter_RequireObject(stackTop, 2);
                    ScriptInterpreter_CheckCallDepth();
                    ScriptInterpreter_SaveCallCodepos(codePos);
                    marker = stackTop - argumentCount - 1;
                    stackTop = ScriptInterpreter_RunThreadCall(
                        stackTop - 1, targetCodePos, parent, marker);
                }

                codePos += SCRIPT_INTERPRETER_FUNCTION_CALL_OPERAND_BYTES;
                break;
            }

            case SCRIPT_OP_THREAD_POINTER:
            case SCRIPT_OP_THREAD_POINTER_METHOD_CONTEXT: {
                if (stackTop->type != CODUO_SCRIPT_VAR_FUNCTION) {
                    coduo_script_variable_type_t badType =
                        (coduo_script_variable_type_t)stackTop->type;
                    if (opcode == SCRIPT_OP_THREAD_POINTER_METHOD_CONTEXT) {
                        ScriptValue_ReleaseValue(stackTop);
                        --stackTop;
                    }
                    Scr_Error(
                        va("%s is not a function pointer",
                           script_variableTypeNames[badType]));
                }

                int32_t argumentCount = ScriptInterpreter_ReadI32(codePos);
                uint8_t *targetCodePos =
                    ScriptInterpreter_CodeposFromPayload(stackTop->payload);
                VariableValue *marker;

                if (opcode == SCRIPT_OP_THREAD_POINTER) {
                    ScriptInterpreter_CheckCallDepth();
                    ScriptInterpreter_SaveCallCodepos(codePos);
                    uint16_t parent =
                        GetSelf(thread);
                    AddRefToObject(parent);
                    marker = stackTop - argumentCount - 1;
                    stackTop = ScriptInterpreter_RunThreadCall(
                        stackTop - 1, targetCodePos, parent, marker);
                } else {
                    --stackTop;
                    uint16_t parent =
                        ScriptInterpreter_RequireObject(stackTop, 2);
                    ScriptInterpreter_CheckCallDepth();
                    ScriptInterpreter_SaveCallCodepos(codePos);
                    marker = stackTop - argumentCount - 1;
                    stackTop = ScriptInterpreter_RunThreadCall(
                        stackTop - 1, targetCodePos, parent, marker);
                }

                codePos += SCRIPT_INTERPRETER_POINTER_CALL_OPERAND_BYTES;
                break;
            }

            case SCRIPT_OP_DROP_TOP:
                ScriptValue_ReleaseValue(stackTop);
                --stackTop;
                break;

            case SCRIPT_OP_CAST_OBJECT:
                fieldRef = CastFieldObject(stackTop);
                --stackTop;
                break;

            case SCRIPT_OP_CAST_BOOL:
                if (stackTop->type == CODUO_SCRIPT_VAR_INT) {
                    stackTop->payload = stackTop->payload != 0 ? qtrue : qfalse;
                } else if (CastBool(stackTop) == qfalse) {
                    ScriptRuntime_RaiseError();
                }
                break;

            case SCRIPT_OP_CAST_INT:
                if (CastInt(stackTop) == qfalse) {
                    ScriptRuntime_RaiseError();
                }
                break;

            case SCRIPT_OP_CAST_FLOAT:
                if (CastFloat(stackTop) == qfalse) {
                    ScriptRuntime_RaiseError();
                }
                break;

            case SCRIPT_OP_CAST_STRING:
                if (CastString(stackTop) == qfalse) {
                    ScriptRuntime_RaiseError();
                }
                break;

            case SCRIPT_OP_BOOL_NOT:
                if (stackTop->type != CODUO_SCRIPT_VAR_INT &&
                    CastBool(stackTop) == qfalse) {
                    ScriptRuntime_RaiseError();
                }
                stackTop->payload = stackTop->payload == 0 ? qtrue : qfalse;
                break;

            case SCRIPT_OP_BIT_NOT:
                if (stackTop->type != CODUO_SCRIPT_VAR_INT) {
                    Scr_Error(
                        va("~ cannot be applied to \"%s\"",
                           script_variableTypeNames[stackTop->type]));
                }
                stackTop->payload =
                    SCRIPT_VALUE_U32_PAYLOAD(~(uint32_t)stackTop->payload);
                break;

            case SCRIPT_OP_JUMP_ON_FALSE:
            case SCRIPT_OP_SHORT_CIRCUIT_AND:
            case SCRIPT_OP_SHORT_CIRCUIT_OR: {
                int32_t offset = ScriptInterpreter_ReadI32(codePos);
                if (stackTop->type != CODUO_SCRIPT_VAR_INT &&
                    CastBool(stackTop) == qfalse) {
                    ScriptRuntime_RaiseError();
                }

                qboolean value = stackTop->payload != 0 ? qtrue : qfalse;
                if (opcode == SCRIPT_OP_JUMP_ON_FALSE) {
                    if (value == qfalse) {
                        codePos += offset + sizeof(int32_t);
                    } else {
                        codePos += sizeof(int32_t);
                    }
                    --stackTop;
                } else if ((opcode == SCRIPT_OP_SHORT_CIRCUIT_OR &&
                            value != qfalse) ||
                           (opcode == SCRIPT_OP_SHORT_CIRCUIT_AND &&
                            value == qfalse)) {
                    codePos += offset + sizeof(int32_t);
                } else {
                    --stackTop;
                    codePos += sizeof(int32_t);
                }
                break;
            }

            case SCRIPT_OP_JUMP_ON_TRUE_BACK: {
                int32_t offset = ScriptInterpreter_ReadI32(codePos);
                if (stackTop->type != CODUO_SCRIPT_VAR_INT &&
                    CastBool(stackTop) == qfalse) {
                    script_errorParameterIndex = 1;
                    ScriptRuntime_RaiseError();
                }
                if (stackTop->payload == 0) {
                    --stackTop;
                    codePos += sizeof(int32_t);
                } else {
                    --stackTop;
                    if ((int32_t)(rdtsc() -
                                  script_loopWatchdogTick) >= 0) {
                        codePos += offset;
                    } else if (script_loopWatchdogWarningFlag != 0) {
                        Com_Printf(
                            "script runtime warning: potential infinite loop in script.\n");
                        Scr_PrintPrevCodePos(0, codePos, 0);
                        script_loopWatchdogTick =
                            rdtsc();
                        codePos += offset;
                    } else if (script_runtimeDeveloperFlag == 0) {
                        Com_Printf(
                            "script runtime error: potential infinite loop in script - killing thread.\n");
                        Scr_PrintPrevCodePos(0, codePos, 0);
                        script_loopWatchdogTick =
                            rdtsc();
                        while (true) {
                            VariableValue returnValue;
                            returnValue.payload =
                                SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE;
                            returnValue.type = CODUO_SCRIPT_VAR_UNDEFINED;
                            if (ScriptInterpreter_ReturnFromFrame(
                                    &stackTop, &codePos, &thread, stackBase,
                                    &returnValue) != qfalse) {
                                return thread;
                            }
                        }
                    } else {
                        Scr_TerminalError("potential infinite loop in script");
                    }
                }
                break;
            }

            case SCRIPT_OP_JUMP:
                codePos += ScriptInterpreter_ReadI32(codePos);
                break;

            case SCRIPT_OP_JUMP_BACK:
                if ((int32_t)(rdtsc() -
                              script_loopWatchdogTick) >= 0) {
                    codePos += ScriptInterpreter_ReadI32(codePos);
                } else if (script_loopWatchdogWarningFlag != 0) {
                    Com_Printf(
                        "script runtime warning: potential infinite loop in script.\n");
                    Scr_PrintPrevCodePos(0, codePos, 0);
                    script_loopWatchdogTick = rdtsc();
                    codePos += ScriptInterpreter_ReadI32(codePos);
                } else if (script_runtimeDeveloperFlag == 0) {
                    Com_Printf(
                        "script runtime error: potential infinite loop in script - killing thread.\n");
                    Scr_PrintPrevCodePos(0, codePos, 0);
                    script_loopWatchdogTick = rdtsc();
                    while (true) {
                        VariableValue returnValue;
                        returnValue.payload =
                            SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE;
                        returnValue.type = CODUO_SCRIPT_VAR_UNDEFINED;
                        if (ScriptInterpreter_ReturnFromFrame(
                                &stackTop, &codePos, &thread, stackBase,
                                &returnValue) != qfalse) {
                            return thread;
                        }
                    }
                } else {
                    Scr_TerminalError("potential infinite loop in script");
                }
                break;

            case SCRIPT_OP_INC:
            case SCRIPT_OP_DEC: {
                ++stackTop;
                GetVariableFieldValue(fieldRef, stackTop);
                if (stackTop->type != CODUO_SCRIPT_VAR_INT) {
                    Scr_Error(
                        va(opcode == SCRIPT_OP_INC
                               ? "++ must be applied to an int (applied to %s)"
                               : "-- must be applied to an int (applied to %s)",
                           script_variableTypeNames[stackTop->type]));
                }
                stackTop->payload = SCRIPT_VALUE_U32_PAYLOAD(
                    (uint32_t)stackTop->payload +
                    (opcode == SCRIPT_OP_INC ? 1u : (uint32_t)-1));
                break;
            }

            case SCRIPT_OP_BIT_OR:
            case SCRIPT_OP_BIT_XOR:
            case SCRIPT_OP_BIT_AND:
            case SCRIPT_OP_SHIFT_LEFT:
            case SCRIPT_OP_SHIFT_RIGHT: {
                VariableValue *left = stackTop - 1;
                if (CastWeakerPair(left) == qfalse) {
                    ScriptRuntime_RaiseError();
                }
                if (left->type != CODUO_SCRIPT_VAR_INT) {
                    UnmatchingTypesError(left, stackTop);
                    ScriptRuntime_RaiseError();
                }
                uint32_t leftValue = (uint32_t)left->payload;
                uint32_t right = (uint32_t)stackTop->payload;
                switch (opcode) {
                case SCRIPT_OP_BIT_OR:
                    left->payload = SCRIPT_VALUE_U32_PAYLOAD(leftValue | right);
                    break;
                case SCRIPT_OP_BIT_XOR:
                    left->payload = SCRIPT_VALUE_U32_PAYLOAD(leftValue ^ right);
                    break;
                case SCRIPT_OP_BIT_AND:
                    left->payload = SCRIPT_VALUE_U32_PAYLOAD(leftValue & right);
                    break;
                case SCRIPT_OP_SHIFT_LEFT:
                    left->payload = SCRIPT_VALUE_U32_PAYLOAD(
                        leftValue << (right & SCRIPT_INTERPRETER_SHIFT_MASK));
                    break;
                case SCRIPT_OP_SHIFT_RIGHT:
                    left->payload = SCRIPT_VALUE_U32_PAYLOAD(
                        coduo_int32_sar_bits(
                            leftValue,
                            right & SCRIPT_INTERPRETER_SHIFT_MASK));
                    break;
                default:
                    break;
                }
                stackTop = left;
                break;
            }

            case SCRIPT_OP_EQUAL:
                --stackTop;
                if (CheckEquality(stackTop, stackTop + 1) == qfalse) {
                    ScriptRuntime_RaiseError();
                }
                break;

            case SCRIPT_OP_NOT_EQUAL:
                --stackTop;
                if (CastWeakerPair(stackTop) == qfalse) {
                    ScriptRuntime_RaiseError();
                }
                switch (stackTop->type) {
                case CODUO_SCRIPT_VAR_UNDEFINED:
                    stackTop->type = CODUO_SCRIPT_VAR_INT;
                    stackTop->payload = qfalse;
                    break;
                case CODUO_SCRIPT_VAR_STRING:
                case CODUO_SCRIPT_VAR_LOCALIZED_STRING: {
                    uint16_t leftString =
                        (uint16_t)stackTop->payload;
                    uint16_t rightString =
                        (uint16_t)stackTop[1].payload;
                    stackTop->type = CODUO_SCRIPT_VAR_INT;
                    SL_RemoveRefToString(leftString);
                    SL_RemoveRefToString(rightString);
                    stackTop->payload =
                        leftString != rightString ? qtrue : qfalse;
                    break;
                }
                case CODUO_SCRIPT_VAR_VECTOR: {
                    float *leftVector =
                        ScriptInterpreter_Vector(stackTop->payload);
                    float *rightVector =
                        ScriptInterpreter_Vector(stackTop[1].payload);
                    qboolean notEqual =
                        leftVector[0] != rightVector[0] ||
                        leftVector[1] != rightVector[1] ||
                        leftVector[2] != rightVector[2] ? qtrue : qfalse;
                    stackTop->type = CODUO_SCRIPT_VAR_INT;
                    RemoveRefToVector(stackTop->payload);
                    RemoveRefToVector(stackTop[1].payload);
                    stackTop->payload = notEqual;
                    break;
                }
                case CODUO_SCRIPT_VAR_FLOAT: {
                    float leftFloat =
                        ScriptInterpreter_PayloadFloat(stackTop->payload);
                    float rightFloat =
                        ScriptInterpreter_PayloadFloat(stackTop[1].payload);
                    stackTop->type = CODUO_SCRIPT_VAR_INT;
                    stackTop->payload =
                        FUN_080b085c(leftFloat - rightFloat) >=
                                (long double)SCRIPT_INTERPRETER_FLOAT_EQUAL_EPSILON
                            ? qtrue
                            : qfalse;
                    break;
                }
                case CODUO_SCRIPT_VAR_INT:
                    stackTop->payload =
                        (uint32_t)stackTop->payload !=
                                (uint32_t)stackTop[1].payload
                            ? qtrue
                            : qfalse;
                    break;
                case CODUO_SCRIPT_VAR_OBJECT: {
                    uint16_t leftObject =
                        (uint16_t)stackTop->payload;
                    uint16_t rightObject =
                        (uint16_t)stackTop[1].payload;
                    stackTop->type = CODUO_SCRIPT_VAR_INT;
                    RemoveRefToObject(leftObject);
                    RemoveRefToObject(rightObject);
                    stackTop->payload =
                        leftObject != rightObject ? qtrue : qfalse;
                    break;
                }
                case CODUO_SCRIPT_VAR_ANIMATION:
                    stackTop->type = CODUO_SCRIPT_VAR_INT;
                    stackTop->payload =
                        stackTop->payload != stackTop[1].payload ? qtrue
                                                                 : qfalse;
                    break;
                default:
                    UnmatchingTypesError(stackTop, stackTop + 1);
                    ScriptRuntime_RaiseError();
                    break;
                }
                break;

            case SCRIPT_OP_LESS:
            case SCRIPT_OP_GREATER:
            case SCRIPT_OP_LESS_EQUAL:
            case SCRIPT_OP_GREATER_EQUAL: {
                VariableValue *left = stackTop - 1;
                if (CastWeakerPair(left) == qfalse) {
                    ScriptRuntime_RaiseError();
                }

                qboolean result = qfalse;
                if (left->type == CODUO_SCRIPT_VAR_FLOAT) {
                    float lhs = ScriptInterpreter_PayloadFloat(left->payload);
                    float rhs = ScriptInterpreter_PayloadFloat(stackTop->payload);
                    if (opcode == SCRIPT_OP_LESS) {
                        result = lhs < rhs ? qtrue : qfalse;
                    } else if (opcode == SCRIPT_OP_GREATER) {
                        result = rhs < lhs ? qtrue : qfalse;
                    } else if (opcode == SCRIPT_OP_LESS_EQUAL) {
                        result = lhs <= rhs ? qtrue : qfalse;
                    } else {
                        result = rhs <= lhs ? qtrue : qfalse;
                    }
                } else if (left->type == CODUO_SCRIPT_VAR_INT) {
                    int32_t lhs = (int32_t)left->payload;
                    int32_t rhs = (int32_t)stackTop->payload;
                    if (opcode == SCRIPT_OP_LESS) {
                        result = lhs < rhs ? qtrue : qfalse;
                    } else if (opcode == SCRIPT_OP_GREATER) {
                        result = rhs < lhs ? qtrue : qfalse;
                    } else if (opcode == SCRIPT_OP_LESS_EQUAL) {
                        result = lhs <= rhs ? qtrue : qfalse;
                    } else {
                        result = rhs <= lhs ? qtrue : qfalse;
                    }
                } else {
                    UnmatchingTypesError(left, stackTop);
                    ScriptRuntime_RaiseError();
                }
                left->type = CODUO_SCRIPT_VAR_INT;
                left->payload = result;
                stackTop = left;
                break;
            }

            case SCRIPT_OP_PLUS:
            case SCRIPT_OP_MINUS:
            case SCRIPT_OP_MULTIPLY:
            case SCRIPT_OP_DIVIDE:
            case SCRIPT_OP_MODULO: {
                VariableValue *left = stackTop - 1;
                if (CastWeakerPair(left) == qfalse) {
                    ScriptRuntime_RaiseError();
                }

                if (left->type == CODUO_SCRIPT_VAR_INT) {
                    int32_t lhs = (int32_t)left->payload;
                    int32_t rhs = (int32_t)stackTop->payload;
                    switch (opcode) {
                    case SCRIPT_OP_PLUS:
                        left->payload = SCRIPT_VALUE_U32_PAYLOAD(lhs + rhs);
                        break;
                    case SCRIPT_OP_MINUS:
                        left->payload = SCRIPT_VALUE_U32_PAYLOAD(lhs - rhs);
                        break;
                    case SCRIPT_OP_MULTIPLY:
                        left->payload = SCRIPT_VALUE_U32_PAYLOAD(lhs * rhs);
                        break;
                    case SCRIPT_OP_DIVIDE:
                        if (rhs == 0) {
                            left->payload = 0;
                            Scr_Error("divide by 0");
                        }
                        /* ORIGINAL_BINARY_BUG: stock checks only for zero
                         * before IDIV, so INT32_MIN / -1 raises the processor
                         * exception.  See original-bugs/
                         * coduomp-script-integer-division-overflow.md. */
                        left->payload = SCRIPT_VALUE_U32_PAYLOAD(
                            coduomp_script_divide_i32(lhs, rhs));
                        break;
                    case SCRIPT_OP_MODULO:
                        if (rhs == 0) {
                            left->payload = 0;
                            Scr_Error("divide by 0");
                        }
                        /* ORIGINAL_BINARY_BUG: the remainder opcode uses the
                         * same overflowing IDIV quotient.  See original-bugs/
                         * coduomp-script-integer-division-overflow.md. */
                        left->payload = SCRIPT_VALUE_U32_PAYLOAD(
                            coduomp_script_remainder_i32(lhs, rhs));
                        break;
                    default:
                        break;
                    }
                } else if (left->type == CODUO_SCRIPT_VAR_FLOAT) {
                    float lhs = ScriptInterpreter_PayloadFloat(left->payload);
                    float rhs = ScriptInterpreter_PayloadFloat(stackTop->payload);
                    float result = 0.0f;
                    switch (opcode) {
                    case SCRIPT_OP_PLUS:
                        result = lhs + rhs;
                        break;
                    case SCRIPT_OP_MINUS:
                        result = lhs - rhs;
                        break;
                    case SCRIPT_OP_MULTIPLY:
                        result = lhs * rhs;
                        break;
                    case SCRIPT_OP_DIVIDE:
                        if (rhs == 0.0f && rhs == rhs) {
                            left->payload = ScriptInterpreter_FloatPayload(0.0f);
                            Scr_Error("divide by 0");
                        }
                        result = lhs / rhs;
                        break;
                    case SCRIPT_OP_MODULO:
                        UnmatchingTypesError(left, stackTop);
                        ScriptRuntime_RaiseError();
                        break;
                    default:
                        break;
                    }
                    left->payload = ScriptInterpreter_FloatPayload(result);
                } else if (left->type == CODUO_SCRIPT_VAR_VECTOR &&
                           (opcode == SCRIPT_OP_PLUS ||
                            opcode == SCRIPT_OP_MINUS)) {
                    float *leftVector = ScriptInterpreter_Vector(left->payload);
                    float *rightVector = ScriptInterpreter_Vector(stackTop->payload);
                    float *result = AllocVector();
                    coduo_script_value_payload_t resultPayload =
                        (coduo_script_value_payload_t)result;

                    for (int32_t axis = 0; axis < 3; ++axis) {
                        result[axis] =
                            opcode == SCRIPT_OP_PLUS
                                ? leftVector[axis] + rightVector[axis]
                                : leftVector[axis] - rightVector[axis];
                    }

                    RemoveRefToVector(left->payload);
                    RemoveRefToVector(stackTop->payload);
                    left->payload = resultPayload;
                } else if (opcode == SCRIPT_OP_PLUS &&
                           left->type == CODUO_SCRIPT_VAR_STRING) {
                    uint16_t leftString =
                        (uint16_t)left->payload;
                    uint16_t rightString =
                        (uint16_t)stackTop->payload;
                    left->payload = VM_ConcatenateStrings(left);
                    SL_RemoveRefToString(leftString);
                    SL_RemoveRefToString(rightString);
                } else {
                    UnmatchingTypesError(left, stackTop);
                    ScriptRuntime_RaiseError();
                }
                stackTop = left;
                break;
            }

            case SCRIPT_OP_SIZE:
                GetSizeValue(stackTop);
                break;

            case SCRIPT_OP_WAITTILL:
            case SCRIPT_OP_WAITTILLMATCH: {
                if (developerDepth != 0) {
                    Scr_Error(
                        "waittill not allowed in /# ... #/ comment (call as a thread to fix)");
                }

                uint16_t object =
                    ScriptInterpreter_RequireObject(stackTop, 2);
                if ((stackTop - 1)->type != CODUO_SCRIPT_VAR_STRING) {
                    script_errorParameterIndex = 1;
                    Scr_Error(
                        "first parameter of waittill must evaluate to a string");
                }

                uint16_t notifyName =
                    (uint16_t)(stackTop - 1)->payload;
                uint16_t waitSlot =
                    GetVariable(object,
                                            SCRIPT_INTERPRETER_ENDON_NOTIFY_NAME);
                uint16_t waitRoot =
                    GetArray(waitSlot);
                uint16_t notifySlot =
                    GetVariable(waitRoot, notifyName);
                uint16_t notifyBucket =
                    GetArray(notifySlot);
                GetObjectVariable(notifyBucket, currentObject);

                VariableValue objectValue;
                objectValue.type = CODUO_SCRIPT_VAR_OBJECT;
                objectValue.payload = object;

                uint16_t parent =
                    GetSelf(currentObject);
                uint16_t pauseSlot =
                    GetObjectVariable(script_pauseArrayHandle,
                                                  parent);
                uint16_t pauseBucket =
                    GetArray(pauseSlot);
                uint16_t pauseThreadSlot =
                    GetObjectVariable(pauseBucket, currentObject);
                SetNewVariableValue(pauseThreadSlot, &objectValue);

                SetThreadNotifyName(currentObject, notifyName);
                SL_RemoveRefToString(notifyName);
                VM_ArchiveStack(
                    (int32_t)(stackTop - stackBase - 2),
                    codePos, thread, currentObject, stackBase,
                    SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE);
                stackBase[1].type = CODUO_SCRIPT_VAR_UNDEFINED;
                return currentObject;
            }

            case SCRIPT_OP_NOTIFY: {
                uint16_t object =
                    ScriptInterpreter_RequireObject(stackTop, 2);
                if ((stackTop - 1)->type != CODUO_SCRIPT_VAR_STRING) {
                    script_errorParameterIndex = 1;
                    Scr_Error(
                        "first parameter of notify must evaluate to a string");
                }

                uint16_t notifyName =
                    (uint16_t)(stackTop - 1)->payload;
                VM_Notify(object, notifyName, stackTop - 2);
                while (stackTop->type != CODUO_SCRIPT_VAR_CODEPOS) {
                    ScriptValue_ReleaseValue(stackTop);
                    --stackTop;
                }
                --stackTop;
                break;
            }

            case SCRIPT_OP_ENDON: {
                uint16_t object =
                    ScriptInterpreter_RequireObject(stackTop, 1);
                if ((stackTop - 1)->type != CODUO_SCRIPT_VAR_STRING) {
                    Scr_Error(
                        "first parameter of notify must evaluate to a string");
                }

                uint16_t notifyName =
                    (uint16_t)(stackTop - 1)->payload;
                AddRefToObject(currentObject);
                uint16_t endonThread =
                    AllocThread(currentObject);

                uint16_t waitSlot =
                    GetVariable(object,
                                            SCRIPT_INTERPRETER_ENDON_NOTIFY_NAME);
                uint16_t waitRoot =
                    GetArray(waitSlot);
                uint16_t notifySlot =
                    GetVariable(waitRoot, notifyName);
                uint16_t notifyBucket =
                    GetArray(notifySlot);
                GetObjectVariable(notifyBucket, endonThread);
                RemoveRefToObject(endonThread);

                VariableValue objectValue;
                objectValue.type = CODUO_SCRIPT_VAR_OBJECT;
                objectValue.payload = object;

                uint16_t pauseSlot =
                    GetObjectVariable(script_pauseArrayHandle,
                                                  currentObject);
                uint16_t pauseBucket =
                    GetArray(pauseSlot);
                uint16_t pauseThreadSlot =
                    GetObjectVariable(pauseBucket, endonThread);
                SetNewVariableValue(pauseThreadSlot, &objectValue);

                SetThreadNotifyName(endonThread, notifyName);
                SL_RemoveRefToString(notifyName);
                stackTop -= 2;
                break;
            }

            case SCRIPT_OP_PUSH_CODEPOS:
                stackTop = ScriptInterpreter_Push(
                    stackTop, CODUO_SCRIPT_VAR_CODEPOS,
                    SCRIPT_INTERPRETER_STACK_SENTINEL_VALUE);
                break;

            case SCRIPT_OP_SWITCH_JUMP:
            {
                /* ORIGINAL_BINARY_BUG: EmitSwitchStatement patches a full
                 * dword displacement, but stock VM_Execute sign-extends only
                 * the low word here. */
                uint8_t *table = codePos + ScriptInterpreter_ReadI16(codePos);
                /* ORIGINAL_BINARY_BUG: the emitted unsigned word is consumed
                 * as a signed countdown; negative counts keep traversing. */
                int32_t caseCount = ScriptInterpreter_ReadI16(table - sizeof(int16_t));
                switchCaseCount = caseCount;
                uint32_t switchValue;
                codePos = table;

                if (stackTop->type == CODUO_SCRIPT_VAR_STRING) {
                    switchValue = stackTop->payload;
                    SL_RemoveRefToString(
                        (uint16_t)stackTop->payload);
                } else if (stackTop->type == CODUO_SCRIPT_VAR_INT) {
                    if (IsValidArrayIndex((int32_t)stackTop->payload) ==
                        qfalse) {
                        Scr_Error(
                            va("switch index %d out of range",
                               (int32_t)stackTop->payload));
                    }
                    switchValue =
                        GetInternalVariableIndex(
                            (int32_t)stackTop->payload);
                } else {
                    Scr_Error(
                        va("cannot switch on %s",
                           script_variableTypeNames[stackTop->type]));
                }

                --stackTop;
                while (caseCount != 0) {
                    const uint8_t *entry =
                        codePos;
                    uint32_t caseValue =
                        ScriptInterpreter_ReadU32(entry);
                    uint8_t *caseCodePos =
                        ScriptInterpreter_CodeposFromPayload(
                            ScriptInterpreter_ReadValuePayload(
                                entry + sizeof(uint32_t)));

                    if (caseValue == switchValue ||
                        (caseCount == 1 && caseValue == 0)) {
                        codePos = caseCodePos;
                        break;
                    }

                    codePos += SCRIPT_INTERPRETER_SWITCH_CASE_ENTRY_BYTES;
                    --caseCount;
                }
                break;
            }

            case SCRIPT_OP_SWITCH_TABLE:
                /* ORIGINAL_BINARY_BUG: stock sign-extends the narrowed case
                 * count and applies that signed bytecode displacement. */
                codePos +=
                    sizeof(int16_t) +
                    ScriptInterpreter_ReadI16(codePos) *
                        SCRIPT_INTERPRETER_SWITCH_CASE_ENTRY_BYTES;
                break;

            case SCRIPT_OP_VECTOR:
                CastVector2(stackTop - 2);
                stackTop -= 2;
                break;

            case SCRIPT_OP_NOP:
                break;

            case SCRIPT_OP_DEVELOPER_COMMAND: {
                script_frameBackupCodepos[script_callStackDepth] = codePos;
                int32_t codeOffset = (int32_t)(codePos - script_codeBase) - 1;
                uint8_t *developerCode = script_developerOpcodePatchTable[codeOffset];
                script_frameBackupOpcode[script_callStackDepth] =
                    developerCode[-1];
                developerDepth++;
                codePos = developerCode;
                break;
            }

            case SCRIPT_OP_DEFERRED_DEVELOPER_CHECK:
                codePos = script_frameBackupCodepos[script_callStackDepth];
                opcode = script_frameBackupOpcode[script_callStackDepth];
                script_frameBackupCodepos[script_callStackDepth] = 0;
                developerDepth--;
                break;
            }
        } catch (const ScriptErrorClass &) {
            if (script_forceErrorReport != 0) {
                RuntimeError(
                    codePos, script_errorParameterIndex, script_errorMessage,
                    script_errorSource);
            }
            ScriptInterpreter_CleanupAfterError(opcode, &stackTop, &codePos,
                                                &fieldRef,
                                                switchCaseCount);
            ScriptInterpreter_ReportAndContinue(codePos);
        }
    }
}
