code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2016 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "anim.h"
#include "cmdline.h"
#include "colmatch.h"
#include "communityapi.h"
#include "compat.h"
#include "duke3d.h"
#include "gamestructures.h"
#include "input.h"
#include "menus.h"
#include "microprofile.h"
#include "osdcmds.h"
#include "savegame.h"
#include "scriplib.h"
#include "vfs.h"

#if KRANDDEBUG
# define GAMEEXEC_INLINE
# define GAMEEXEC_STATIC
#else
# define GAMEEXEC_INLINE inline
# define GAMEEXEC_STATIC static
#endif

#if MICROPROFILE_ENABLED != 0
extern MicroProfileToken g_eventTokens[MAXEVENTS];
extern MicroProfileToken g_eventCounterTokens[MAXEVENTS];
extern MicroProfileToken g_actorTokens[MAXTILES];
extern MicroProfileToken g_statnumTokens[MAXSTATUS];
#if 0
extern MicroProfileToken g_instTokens[CON_END];
#endif
#endif

vmstate_t vm;

int32_t g_tw;
int32_t g_currentEvent = -1;

intptr_t const *insptr;

int32_t g_returnVarID    = -1;  // var ID of "RETURN"
int32_t g_weaponVarID    = -1;  // var ID of "WEAPON"
int32_t g_worksLikeVarID = -1;  // var ID of "WORKSLIKE"
int32_t g_zRangeVarID    = -1;  // var ID of "ZRANGE"
int32_t g_angRangeVarID  = -1;  // var ID of "ANGRANGE"
int32_t g_aimAngleVarID  = -1;  // var ID of "AUTOAIMANGLE"
int32_t g_lotagVarID     = -1;  // var ID of "LOTAG"
int32_t g_hitagVarID     = -1;  // var ID of "HITAG"
int32_t g_textureVarID   = -1;  // var ID of "TEXTURE"
int32_t g_thisActorVarID = -1;  // var ID of "THISACTOR"
int32_t g_structVarIDs   = -1;

GAMEEXEC_STATIC void VM_Execute(int loop = false);

void VM_ScriptInfo(intptr_t const * const ptr, int const range)
{
    if (!ptr || (g_currentEvent == -1 && insptr == nullptr))
        return;

    int foundInst = 0;
    int lastLine = 0;
    char buf[128];

    for (auto pScript = max<intptr_t const *>(ptr - (range >> 2), apScript),
                p_end   = min<intptr_t const *>(ptr + (range >> 1) + (range >> 2), apScript + g_scriptSize);
            pScript < p_end;
            ++pScript)
    {
        auto &v = *pScript;
        int const lineNum = VM_DECODE_LINE_NUMBER(v);
        int const vmInst  = VM_DECODE_INST(v);

        if (lineNum && lineNum >= lastLine && lineNum < g_totalLines && (unsigned)vmInst < CON_OPCODE_END)
        {
            lastLine = lineNum;

            if (foundInst == 1)
                VLOG_F(LOG_VM, "%s", buf);
            else if (foundInst == 2)
                LOG_F(ERROR, "%s", buf);

            foundInst = 1;

            if (lineNum == VM_DECODE_LINE_NUMBER(g_tw) || vmInst == VM_DECODE_INST(g_tw))
                foundInst++;

            Bsprintf(buf, "%8s:%5d: [0x%04x] %s", VM_FILENAME(pScript), lineNum, int32_t((pScript - apScript) * sizeof(intptr_t)), VM_GetKeywordForID(vmInst));
        }
        else if (foundInst)
        {
            char buf2[16];

            if (bitmap_test(bitptr, pScript - apScript))
                Bsprintf(buf2, " [0x%04x]", int32_t((pScript - apScript) * sizeof(intptr_t)));
            else Bsprintf(buf2, " %d", int32_t(*pScript));

            Bstrcat(buf, buf2);
        }
    }

    if (ptr == insptr)
    {
        if (vm.pUSprite)
            VLOG_F(LOG_VM, "current actor: %d (%d)", vm.spriteNum, vm.pUSprite->picnum);
    }
}

static void VM_DeleteSprite(int const spriteNum, int const playerNum)
{
    if (EDUKE32_PREDICT_FALSE((unsigned) spriteNum >= MAXSPRITES))
        return;

    // if player was set to squish, first stop that...
    if (EDUKE32_PREDICT_FALSE(playerNum >= 0 && g_player[playerNum].ps->actorsqu == spriteNum))
        g_player[playerNum].ps->actorsqu = -1;

    A_DeleteSprite(spriteNum);
}

intptr_t apScriptEvents[MAXEVENTS];

static uspritetype dummy_sprite;
static actor_t     dummy_actor;

static inline void VM_DummySprite(void)
{
    vm.pUSprite = &dummy_sprite;
    vm.pActor   = &dummy_actor;
    vm.pData    = &dummy_actor.t_data[0];
}

// verification that the event actually exists happens elsewhere
static FORCE_INLINE int32_t VM_EventInlineInternal__(int const eventNum, int const spriteNum, int const playerNum,
                                                       int const playerDist = -1, int32_t returnValue = 0)
{
    ERROR_CONTEXT("VM event execution", &EventNames[eventNum][0]);
    MICROPROFILE_SCOPE_TOKEN(g_eventTokens[eventNum]);
    MicroProfileCounterAdd(g_eventCounterTokens[eventNum], 1);

    vmstate_t const newVMstate = { spriteNum, playerNum, playerDist, 0,
                                   &sprite[spriteNum&(MAXSPRITES-1)],
                                   &actor[spriteNum&(MAXSPRITES-1)].t_data[0],
                                   g_player[playerNum&(MAXPLAYERS-1)].ps,
                                   &actor[spriteNum&(MAXSPRITES-1)] };

    auto &globalReturn = aGameVars[g_returnVarID].global;

    struct
    {
        vmstate_t vm;
        intptr_t globalReturn;
        int eventNum;
        intptr_t const *insptr;
    } const saved = { vm, globalReturn, g_currentEvent, insptr };

    vm = newVMstate;
    g_currentEvent = eventNum;
    insptr = apScript + apScriptEvents[eventNum];
    globalReturn = returnValue;

    if ((unsigned)spriteNum >= MAXSPRITES)
        VM_DummySprite();

    if ((unsigned)playerNum >= (unsigned)g_mostConcurrentPlayers)
        vm.pPlayer = g_player[0].ps;

    VM_Execute(true);

    if (vm.flags & VM_KILL)
        VM_DeleteSprite(vm.spriteNum, vm.playerNum);

    // restoring these needs to happen after VM_DeleteSprite() due to event recursion
    returnValue = globalReturn;

    vm             = saved.vm;
    globalReturn   = saved.globalReturn;
    g_currentEvent = saved.eventNum;
    insptr         = saved.insptr;

    return returnValue;
}

// the idea here is that the compiler inlines the call to VM_EventInlineInternal__() and gives us a set of
// functions which are optimized further based on distance/return having values known at compile time

int32_t VM_ExecuteEvent(int const nEventID, int const spriteNum, int const playerNum, int const nDist, int32_t const nReturn)
{
    return VM_EventInlineInternal__(nEventID, spriteNum, playerNum, nDist, nReturn);
}

int32_t VM_ExecuteEvent(int const nEventID, int const spriteNum, int const playerNum, int const nDist)
{
    return VM_EventInlineInternal__(nEventID, spriteNum, playerNum, nDist);
}

int32_t VM_ExecuteEvent(int const nEventID, int const spriteNum, int const playerNum)
{
    return VM_EventInlineInternal__(nEventID, spriteNum, playerNum);
}

int32_t VM_ExecuteEventWithValue(int const nEventID, int const spriteNum, int const playerNum, int32_t const nReturn)
{
    return VM_EventInlineInternal__(nEventID, spriteNum, playerNum, -1, nReturn);
}

static int VM_CheckSquished(void)
{
    auto const pSector = (usectorptr_t)&sector[vm.pSprite->sectnum];

    if (pSector->lotag == ST_23_SWINGING_DOOR || (vm.pSprite->picnum == APLAYER && ud.noclip) ||
        (pSector->lotag == ST_1_ABOVE_WATER && !A_CheckNoSE7Water(vm.pUSprite, vm.pSprite->sectnum, pSector->lotag, NULL)))
        return 0;

    int32_t floorZ = pSector->floorz;
    int32_t ceilZ  = pSector->ceilingz;
#ifdef YAX_ENABLE
    int16_t cb, fb;

    yax_getbunches(vm.pSprite->sectnum, &cb, &fb);

    if (cb >= 0 && (pSector->ceilingstat&512)==0)  // if ceiling non-blocking...
        ceilZ -= ZOFFSET5;  // unconditionally don't squish... yax_getneighborsect is slowish :/
    if (fb >= 0 && (pSector->floorstat&512)==0)
        floorZ += ZOFFSET5;
#endif

    floorZ = max(floorZ, vm.pActor->floorz);
    ceilZ  = min(ceilZ, vm.pActor->ceilingz);

    if (vm.pSprite->pal == 1 ? ((floorZ - ceilZ >= ZOFFSET5) | (pSector->lotag & 32768u)) : (floorZ - ceilZ >= ZOFFSET4))
        return 0;

    if (vm.pPlayer->ftq != QUOTE_DEAD)
        P_DoQuote(QUOTE_SQUISHED, vm.pPlayer);

    if (vm.spriteNum == vm.pPlayer->i && vm.pPlayer->dead_flag)
        return 0;

    if (A_CheckEnemySprite(vm.pSprite))
        vm.pSprite->xvel = 0;

#ifndef EDUKE32_STANDALONE
    if (EDUKE32_PREDICT_FALSE(vm.pSprite->pal == 1)) // frozen
    {
        vm.pActor->htpicnum = SHOTSPARK1;
        vm.pActor->htextra  = 1;
        return 0;
    }
#endif

    return 1;
}

GAMEEXEC_STATIC GAMEEXEC_INLINE void P_ForceAngle(DukePlayer_t *pPlayer)
{
    int const nAngle = 128-(krand()&255);

    pPlayer->q16horiz           += F16(64);
    pPlayer->return_to_center = 9;
    pPlayer->rotscrnang       = nAngle >> 1;
    pPlayer->look_ang         = pPlayer->rotscrnang;
}

// wow, this function sucks
#ifdef __cplusplus
extern "C"
#endif
int A_Dodge(spritetype * const);
int A_Dodge(spritetype * const pSprite)
{
    if (A_CheckEnemySprite(pSprite) && pSprite->extra <= 0)  // hack
        return 0;

    vec2_t const msin = { sintable[(pSprite->ang + 512) & 2047], sintable[pSprite->ang & 2047] };

    for (native_t nexti, SPRITES_OF_STAT_SAFE(STAT_PROJECTILE, i, nexti)) //weapons list
    {
        if (OW(i) == i)
            continue;

        vec2_t const b = { SX(i) - pSprite->x, SY(i) - pSprite->y };
        vec2_t const v = { sintable[(SA(i) + 512) & 2047], sintable[SA(i) & 2047] };

        if (((msin.x * b.x) + (msin.y * b.y) >= 0) && ((v.x * b.x) + (v.y * b.y) < 0))
        {
            if (klabs((v.x * b.y) - (v.y * b.x)) < 65536 << 6)
            {
                pSprite->ang -= 512+(krand()&1024);
                return 1;
            }
        }
    }

    return 0;
}

int A_GetFurthestAngle(int const spriteNum, int const angDiv)
{
    auto const pSprite = (uspriteptr_t)&sprite[spriteNum];

    if (pSprite->picnum != APLAYER && (AC_COUNT(actor[spriteNum].t_data)&63) > 2)
        return (pSprite->ang + 1024) & 2047;

    int       furthestAngle = 0;
    int const angIncs       = tabledivide32_noinline(2048, angDiv);
    int32_t   greatestDist  = INT32_MIN;
    hitdata_t hit;

    for (native_t j = pSprite->ang; j < (2048 + pSprite->ang); j += angIncs)
    {
        vec3_t origin = pSprite->xyz;
        origin.z -= ZOFFSET3;
        hitscan(&origin, pSprite->sectnum, sintable[(j + 512) & 2047], sintable[j & 2047], 0, &hit, CLIPMASK1);

        int const hitDist = klabs(hit.x-pSprite->x) + klabs(hit.y-pSprite->y);

        if (hitDist > greatestDist)
        {
            greatestDist = hitDist;
            furthestAngle = j;
        }
    }

    return furthestAngle & 2047;
}

int A_FurthestVisiblePoint(int const spriteNum, uspriteptr_t const ts, vec2_t * const vect)
{
    if (AC_COUNT(actor[spriteNum].t_data)&63)
        return -1;

    auto const pnSprite = (uspriteptr_t)&sprite[spriteNum];

    hitdata_t hit;
    int const angincs = 128;
//    ((!g_netServer && ud.multimode < 2) && ud.player_skill < 3) ? 2048 / 2 : tabledivide32_noinline(2048, 1 + (krand() & 1));

    for (native_t j = ts->ang; j < (2048 + ts->ang); j += (angincs /*-(krand()&511)*/))
    {
        vec3_t origin = *(const vec3_t *)ts;
        origin.z -= ZOFFSET2;
        hitscan(&origin, ts->sectnum, sintable[(j + 512) & 2047], sintable[j & 2047], 16384 - (krand() & 32767), &hit, CLIPMASK1);

        if (hit.sect < 0)
            continue;

        int const d  = FindDistance2D(hit.x - ts->x, hit.y - ts->y);
        int const da = FindDistance2D(hit.x - pnSprite->x, hit.y - pnSprite->y);

        if (d < da)
        {
            if (cansee(hit.x, hit.y, hit.z, hit.sect, pnSprite->x, pnSprite->y, pnSprite->z - ZOFFSET2, pnSprite->sectnum))
            {
                *vect = hit.xy;
                return hit.sect;
            }
        }
    }

    return -1;
}

//zhit_t zhit[MAXSPRITES];
//static uint8_t zhseen[(MAXSECTORS+7)>>3];

//static inline uint16_t getsectorzsum(int const sectnum)
//{
//    Bassert(!bitmap_test(zhseen, sectnum));
//
//    int  crc = 0;
//    auto sec = (usectortype *)&sector[sectnum];
//
//    bitmap_set(zhseen, sectnum);
//
//    // might as well do the full struct here regardless of ZSUM_FULL_STRUCTS, because reading cstat pulls it into cache
//    for (int SPRITES_OF_SECT(sectnum, i))
//        if (sprite[i].cstat & CSTAT_SPRITE_BLOCK)
//#if 1 // #ifdef ZSUM_FULL_STRUCTS
//            crc = getcrc16(&sprite[i], sizeof(spritetype), crc);
//#else
//            crc = getcrc16(&spritechanged[i], sizeof(spritechanged[0]), crc);
//#endif // ZSUM_FULL_STRUCTS
//
//#ifdef ZSUM_FULL_STRUCTS
//    for (int startwall = sec->wallptr, endwall = sec->wallnum + startwall, i = startwall; i < endwall; i++)
//        crc = getcrc16(&wall[i], sizeof(walltype), crc);
//    crc = getcrc16(&sector[sectnum], sizeof(sectortype), crc);
//#else
//    for (int startwall = sec->wallptr, endwall = sec->wallnum + startwall, i = startwall; i < endwall; i++)
//        crc = getcrc16(&wallchanged[i], sizeof(wallchanged[0]), crc);
//    crc = getcrc16(&sectorchanged[sectnum], sizeof(sectorchanged[0]), crc);
//#endif // ZSUM_FULL_STRUCTS
//
//    return((uint16_t)(crc&65535));
//}

//static uint16_t A_GetZSum(int const spriteNum)
//{
//    auto const pSprite = &sprite[spriteNum];
//    auto const pzhit   = &zhit[spriteNum];
//    auto const pActor  = &actor[spriteNum];
//
//    Bmemset(zhseen, 0, sizeof(zhseen));
//
//    int  crc = getsectorzsum(pSprite->sectnum);
//    auto sec = (usectortype *)&sector[pSprite->sectnum];
//
//    for (int startwall = sec->wallptr, endwall = sec->wallnum + startwall, i = startwall; i < endwall; i++)
//        if ((unsigned)wall[i].nextsector < MAXSECTORS)
//            if (!bitmap_test(zhseen, wall[i].nextsector))
//                updatecrc16(crc, getsectorzsum(wall[i].nextsector));
//
//    if ((pzhit->hi & 49152) == 49152)
//    {
//        int const  hiZspr  = pzhit->hi&(MAXSPRITES-1);
//        auto const phiZspr = &sprite[hiZspr];
//        if (phiZspr->sectnum != MAXSECTORS && !bitmap_test(zhseen, phiZspr->sectnum))
//            updatecrc16(crc, getsectorzsum(phiZspr->sectnum));
//    }
//
//    if ((pzhit->lo & 49152) == 49152)
//    {
//        int const  loZspr  = pzhit->lo&(MAXSPRITES-1);
//        auto const ploZspr = &sprite[loZspr];
//        if (ploZspr->sectnum != MAXSECTORS && !bitmap_test(zhseen, ploZspr->sectnum))
//            updatecrc16(crc, getsectorzsum(ploZspr->sectnum));
//    }
//
//    crc = getcrc16(&sprite[spriteNum].xyz, sizeof(vec3_t), crc);
//    crc = getcrc16(&pActor->ceilingz, sizeof(int32_t), crc);
//    crc = getcrc16(&pActor->floorz, sizeof(int32_t), crc);
//
//    return((uint16_t)(crc&65535));
//}

void VM_GetZRange(int const spriteNum, int32_t* const ceilhit, int32_t* const florhit, int const wallDist)
{
    auto const pSprite = &sprite[spriteNum];
    auto const pActor  = &actor[spriteNum];
    //auto const pz     = &zhit[spriteNum];
    //uint16_t   zsum   = A_GetZSum(spriteNum);

    //static uint32_t cnt1, cnt2;

    //if (pz->zsum == zsum)
    //{
    //    *florhit = pz->lo;
    //    *ceilhit = pz->hi;
    //    //OSD_Printf("cnt1 %d cnt2 %d\n", cnt1++, cnt2);
    //    return;
    //}
    //OSD_Printf("cnt1 %d cnt2 %d\n", cnt1, cnt2++);
    int const ocstat = pSprite->cstat;

    pSprite->cstat = 0;
    pSprite->z -= AC_FZOFFSET(spriteNum);

    getzrange(&pSprite->xyz, pSprite->sectnum, &pActor->ceilingz, ceilhit, &pActor->floorz, florhit, wallDist, CLIPMASK0);

    pSprite->z += AC_FZOFFSET(spriteNum);
    pSprite->cstat = ocstat;

    //pz->hi = *ceilhit;
    //pz->lo = *florhit;
    //pz->zsum = zsum;
}

void A_GetZLimits(int const spriteNum)
{
    auto const pSprite = &sprite[spriteNum];
    auto const pActor  = &actor[spriteNum];
    int const  clipDist = A_GetClipdist(spriteNum);
    int32_t    ceilhit = 0, florhit = 0;
    auto const oceilz   = pActor->ceilingz;

    VM_GetZRange(spriteNum, &ceilhit, &florhit, pSprite->statnum == STAT_PROJECTILE ? clipDist << 3 : clipDist);

    if (pSprite->xvel || pSprite->zvel || pActor->bpos != pSprite->xyz)
    {
        pActor->flags &= ~SFLAG_NOFLOORSHADOW;

        if ((florhit & 49152) == 49152 && (sprite[florhit & (MAXSPRITES - 1)].cstat & 48) == 0)
        {
            auto const hitspr = (uspriteptr_t)&sprite[florhit & (MAXSPRITES - 1)];

            florhit &= (MAXSPRITES - 1);

            // If a non-projectile would fall onto non-frozen enemy OR an enemy onto a player...
            if ((A_CheckEnemySprite(hitspr) && hitspr->pal != 1 && pSprite->statnum != STAT_PROJECTILE)
                || (hitspr->picnum == APLAYER && A_CheckEnemySprite(pSprite)))
            {
                pActor->flags |= SFLAG_NOFLOORSHADOW;  // No shadows on actors
                pSprite->xvel = -256;                  // SLIDE_ABOVE_ENEMY
                A_SetSprite(spriteNum, CLIPMASK0);
            }
            else if (pSprite->statnum == STAT_PROJECTILE && hitspr->picnum == APLAYER && pSprite->owner == florhit)
            {
                pActor->ceilingz = sector[pSprite->sectnum].ceilingz;
                pActor->floorz   = sector[pSprite->sectnum].floorz;
            }
        }
    }

    // in E1L1, the dumpster fire sprites break after calling this function because the cardboard boxes
    // are a few units higher than the fire and are detected as the "ceiling"
    // unfortunately, this trips the "ifgapzl 16 break" in "state firestate"
    if ((ceilhit&49152) == 49152)
    {
        auto const pCeil = &sprite[ceilhit&(MAXSPRITES-1)];
        if ((pCeil->cstat&48) == 0 && pCeil->z >= pActor->floorz)
            pActor->ceilingz = oceilz;
    }
}

void A_Fall(int const spriteNum)
{
    MICROPROFILE_SCOPEI("VM", EDUKE32_FUNCTION, MP_AUTO);

    auto const pSprite = &sprite[spriteNum];
    int spriteGravity = g_spriteGravity;

    if (EDUKE32_PREDICT_FALSE(G_CheckForSpaceFloor(pSprite->sectnum)))
        spriteGravity = 0;
    else if (sector[pSprite->sectnum].lotag == ST_2_UNDERWATER || EDUKE32_PREDICT_FALSE(G_CheckForSpaceCeiling(pSprite->sectnum)))
        spriteGravity = g_spriteGravity/6;

    int32_t ceilhit, florhit;
    VM_GetZRange(spriteNum, &ceilhit, &florhit, A_GetClipdist(spriteNum));

#ifdef YAX_ENABLE
    int fbunch = (sector[pSprite->sectnum].floorstat&512) ? -1 : yax_getbunch(pSprite->sectnum, YAX_FLOOR);
#endif

    if ((pSprite->z < actor[spriteNum].floorz - AC_FZOFFSET(spriteNum))
#ifdef YAX_ENABLE
            | (fbunch >= 0)
#endif
       )
    {
        if (sector[pSprite->sectnum].lotag == ST_2_UNDERWATER && pSprite->zvel > 3122)
            pSprite->zvel = 3144;
        pSprite->z += pSprite->zvel = min(ACTOR_MAXFALLINGZVEL, pSprite->zvel+spriteGravity);
    }

#ifdef YAX_ENABLE
    if (fbunch >= 0)
        setspritez(spriteNum, &pSprite->xyz);
    else
#endif
        if (pSprite->z >= actor[spriteNum].floorz - AC_FZOFFSET(spriteNum))
        {
            pSprite->z = actor[spriteNum].floorz - AC_FZOFFSET(spriteNum);
            pSprite->zvel = 0;
        }
}

int __fastcall G_GetAngleDelta(int currAngle, int newAngle)
{
    currAngle &= 2047;
    newAngle &= 2047;

    if (klabs(currAngle-newAngle) < 1024)
    {
//        OSD_Printf("G_GetAngleDelta() returning %d\n",na-a);
        return newAngle-currAngle;
    }

    if (newAngle > 1024)
        newAngle -= 2048;
    if (currAngle > 1024)
        currAngle -= 2048;

//    OSD_Printf("G_GetAngleDelta() returning %d\n",na-a);
    return newAngle-currAngle;
}

GAMEEXEC_STATIC void VM_AlterAng(int32_t const moveFlags)
{
    int const elapsedTics = (AC_COUNT(vm.pData))&31;

    if (EDUKE32_PREDICT_FALSE((unsigned)AC_MOVE_ID(vm.pData) >= (unsigned)g_scriptSize-1))

    {
        AC_MOVE_ID(vm.pData) = 0;
        VLOG_F(LOG_VM, "bad moveptr for actor %d (%d)!", vm.spriteNum, vm.pUSprite->picnum);
        return;
    }

    auto const moveptr = apScript + AC_MOVE_ID(vm.pData);
    auto &hvel    = moveptr[0];
    auto &vvel    = moveptr[1];

    vm.pSprite->xvel += (hvel - vm.pSprite->xvel)/5;
    if (vm.pSprite->zvel < 648)
        vm.pSprite->zvel += ((vvel<<4) - vm.pSprite->zvel)/5;

    if (A_CheckEnemySprite(vm.pSprite) && vm.pSprite->extra <= 0) // hack
        return;

    if (moveFlags&seekplayer)
    {
        int const spriteAngle    = vm.pSprite->ang;
        int const holoDukeSprite = vm.pPlayer->holoduke_on;

        // NOTE: looks like 'owner' is set to target sprite ID...

        vm.pSprite->owner = (holoDukeSprite >= 0
                             && cansee(sprite[holoDukeSprite].x, sprite[holoDukeSprite].y, sprite[holoDukeSprite].z, sprite[holoDukeSprite].sectnum,
                                       vm.pSprite->x, vm.pSprite->y, vm.pSprite->z, vm.pSprite->sectnum))
          ? holoDukeSprite
          : vm.pPlayer->i;

        int const goalAng = (sprite[vm.pSprite->owner].picnum == APLAYER)
                  ? getangle(vm.pActor->lastv.x - vm.pSprite->x, vm.pActor->lastv.y - vm.pSprite->y)
                  : getangle(sprite[vm.pSprite->owner].x - vm.pSprite->x, sprite[vm.pSprite->owner].y - vm.pSprite->y);

        if (vm.pSprite->xvel && vm.pSprite->picnum != DRONE)
        {
            int const angDiff = G_GetAngleDelta(spriteAngle, goalAng);

            if (elapsedTics < 2)
            {
                if (klabs(angDiff) < 256)
                {
                    int const angInc = 128-(krand()&256);
                    vm.pSprite->ang += angInc;
                    if (A_GetHitscanRange(vm.spriteNum) < 844)
                        vm.pSprite->ang -= angInc;
                }
            }
            else if (elapsedTics > 18 && elapsedTics < GAMETICSPERSEC) // choose
            {
                if (klabs(angDiff >> 2) < 128)
                    vm.pSprite->ang = goalAng;
                else
                    vm.pSprite->ang += angDiff >> 2;
            }
        }
        else
            vm.pSprite->ang = goalAng;
    }

    if (elapsedTics < 1)
    {
        if (moveFlags&furthestdir)
        {
            vm.pSprite->ang = A_GetFurthestAngle(vm.spriteNum, 2);
            vm.pSprite->owner = vm.pPlayer->i;
        }

        if (moveFlags&fleeenemy)
            vm.pSprite->ang = A_GetFurthestAngle(vm.spriteNum, 2);
    }
}

static inline void VM_AddAngle(int const shift, int const goalAng)
{
    int angDiff = G_GetAngleDelta(vm.pSprite->ang, goalAng) >> shift;

    if ((angDiff > -8 && angDiff < 0) | (angDiff < 8 && angDiff > 0))
        angDiff *= 2;

    vm.pSprite->ang += angDiff;
}

static inline void VM_FacePlayer(int const shift)
{
    auto& playerVec = (vm.pPlayer->newowner >= 0) ? vm.pPlayer->opos : vm.pPlayer->pos;
    VM_AddAngle(shift, getangle(playerVec.x - vm.pSprite->x, playerVec.y - vm.pSprite->y));
}

static inline int32_t VM_GetCeilZOfSlope(void)
{
    return yax_getceilzofslope(vm.pSprite->sectnum, vm.pSprite->xy);
}

#ifndef EDUKE32_STANDALONE
static inline int32_t VM_GetFlorZOfSlope(void)
{
    return yax_getflorzofslope(vm.pSprite->sectnum, vm.pSprite->xy);
}
#endif

static int32_t A_GetWaterZOffset(int spritenum);

GAMEEXEC_STATIC void VM_Move(void)
{
    MICROPROFILE_SCOPEI("VM", EDUKE32_FUNCTION, MP_AUTO);

    auto const movflagsptr = &AC_MOVFLAGS(vm.pSprite, &actor[vm.spriteNum]);
    // NOTE: test against -1 commented out and later revived in source history
    // XXX: Does its presence/absence break anything? Where are movflags with all bits set created?
    int const movflags = (*movflagsptr == (remove_pointer_t<decltype(movflagsptr)>)-1) ? 0 : *movflagsptr;
    int const deadflag = (A_CheckEnemySprite(vm.pSprite) && vm.pSprite->extra <= 0);

    AC_COUNT(vm.pData)++;

    if ((AC_MOVE_ID(vm.pData) == 0) | (movflags == 0))
    {
        if ((deadflag != 0) | (vm.pActor->bpos.xy != vm.pSprite->xy))
            setsprite(vm.spriteNum, &vm.pSprite->xyz);

        // this fixes the WW2GI tank not facing the player, as it uses move 0
        if (WW2GI && movflags & face_player)
            VM_FacePlayer(2);

        return;
    }

    if (!deadflag)
    {
        if (movflags & face_player)
            VM_FacePlayer(2);

        if (movflags & spin)
            vm.pSprite->ang += sintable[((AC_COUNT(vm.pData) << 3) & 2047)] >> 6;

        if (movflags & face_player_slow)
            VM_FacePlayer(4);

        if ((movflags & jumptoplayer_bits) == jumptoplayer_bits)
        {
            if (AC_COUNT(vm.pData) < 16)
                vm.pSprite->zvel -= (sintable[(512 + (AC_COUNT(vm.pData) << 4)) & 2047] >> 5);
        }

        if (movflags & face_player_smart)
        {
            vec2_t const vect = { vm.pPlayer->pos.x + (vm.pPlayer->vel.x / 768), vm.pPlayer->pos.y + (vm.pPlayer->vel.y / 768) };
            VM_AddAngle(2, getangle(vect.x - vm.pSprite->x, vect.y - vm.pSprite->y));
        }
    }

    if (EDUKE32_PREDICT_FALSE((unsigned)AC_MOVE_ID(vm.pData) >= (unsigned)g_scriptSize-1))
    {
        AC_MOVE_ID(vm.pData) = 0;
        VLOG_F(LOG_VM, "clearing bad moveptr for actor %d (%d)", vm.spriteNum, vm.pUSprite->picnum);
        return;
    }

    auto const moveptr = apScript + AC_MOVE_ID(vm.pData);
    auto &hvel = moveptr[0];
    auto &vvel = moveptr[1];

    if (movflags & geth)
        vm.pSprite->xvel += (hvel - vm.pSprite->xvel) >> 1;

    if (movflags & getv)
        vm.pSprite->zvel += (16 * vvel - vm.pSprite->zvel) >> 1;

    if (movflags&dodgebullet && !deadflag)
        A_Dodge(vm.pSprite);

    if (vm.pSprite->picnum != APLAYER)
        VM_AlterAng(movflags);

    if (vm.pSprite->xvel > -6 && vm.pSprite->xvel < 6)
        vm.pSprite->xvel = 0;

    int badguyp = A_CheckEnemySprite(vm.pSprite);

    if (vm.pSprite->xvel || vm.pSprite->zvel)
    {
        int spriteXvel = vm.pSprite->xvel;
        int angDiff    = vm.pSprite->ang;

#ifndef EDUKE32_STANDALONE
        if (badguyp && (FURY || vm.pSprite->picnum != ROTATEGUN))
        {
            if (!FURY && (vm.pSprite->picnum == DRONE || vm.pSprite->picnum == COMMANDER) && vm.pSprite->extra > 0)
            {
                if (vm.pSprite->picnum == COMMANDER)
                {
                    int32_t nSectorZ;
                    // NOTE: COMMANDER updates both actor[].floorz and
                    // .ceilingz regardless of its zvel.
                    vm.pActor->floorz = nSectorZ = VM_GetFlorZOfSlope();
                    if (vm.pSprite->z > nSectorZ-ZOFFSET3)
                    {
                        vm.pSprite->z = nSectorZ-ZOFFSET3;
                        vm.pSprite->zvel = 0;
                    }

                    vm.pActor->ceilingz = nSectorZ = VM_GetCeilZOfSlope();
                    if (vm.pSprite->z < nSectorZ+(80<<8))
                    {
                        vm.pSprite->z = nSectorZ+(80<<8);
                        vm.pSprite->zvel = 0;
                    }
                }
                else
                {
                    int32_t nSectorZ;
                    // The DRONE updates either .floorz or .ceilingz, not both.
                    if (vm.pSprite->zvel > 0)
                    {
                        vm.pActor->floorz = nSectorZ = VM_GetFlorZOfSlope();
                        if (vm.pSprite->z > nSectorZ-(30<<8))
                            vm.pSprite->z = nSectorZ-(30<<8);
                    }
                    else
                    {
                        vm.pActor->ceilingz = nSectorZ = VM_GetCeilZOfSlope();
                        if (vm.pSprite->z < nSectorZ+(50<<8))
                        {
                            vm.pSprite->z = nSectorZ+(50<<8);
                            vm.pSprite->zvel = 0;
                        }
                    }
                }
            }
            else if ((FURY && badguyp) || vm.pSprite->picnum != ORGANTIC)
#else
        if (badguyp)
        {
#endif
            {
                // All other actors besides ORGANTIC don't update .floorz or
                // .ceilingz here.
                if (vm.pSprite->zvel > 0)
                {
                    if (vm.pSprite->z > vm.pActor->floorz)
                        vm.pSprite->z = vm.pActor->floorz;
                    vm.pSprite->z += A_GetWaterZOffset(vm.spriteNum);
                }
                else if (vm.pSprite->zvel < 0)
                {
                    int const l = VM_GetCeilZOfSlope();

                    if (vm.pSprite->z < l+(66<<8))
                    {
                        vm.pSprite->z = l+(66<<8);
                        vm.pSprite->zvel >>= 1;
                    }
                }
            }

            if (vm.playerDist < 960 && vm.pSprite->xrepeat > 16)
            {
                spriteXvel = -(1024 - vm.playerDist);
                angDiff = getangle(vm.pPlayer->pos.x - vm.pSprite->x, vm.pPlayer->pos.y - vm.pSprite->y);

                if (vm.playerDist < 512)
                {
                    vm.pPlayer->vel.x = 0;
                    vm.pPlayer->vel.y = 0;
                }
                else
                {
                    vm.pPlayer->vel.x = mulscale16(vm.pPlayer->vel.x, vm.pPlayer->runspeed - 0x2000);
                    vm.pPlayer->vel.y = mulscale16(vm.pPlayer->vel.y, vm.pPlayer->runspeed - 0x2000);
                }
            }
            else
#ifndef EDUKE32_STANDALONE
                if (FURY || (vm.pSprite->picnum != DRONE && vm.pSprite->picnum != SHARK && vm.pSprite->picnum != COMMANDER))
#endif
            {
                if (vm.pPlayer->actorsqu == vm.spriteNum)
                    return;

                if (!vvel && !A_CheckSpriteFlags(vm.spriteNum, SFLAG_SMOOTHMOVE))
                {
                    if (AC_COUNT(vm.pData) & 1)
                        return;
                    spriteXvel <<= 1;
                }
            }
        }
        else if (vm.pSprite->picnum == APLAYER)
            if (vm.pSprite->z < vm.pActor->ceilingz+ZOFFSET5)
                vm.pSprite->z = vm.pActor->ceilingz+ZOFFSET5;

        vm.pActor->movflag = A_MoveSprite(vm.spriteNum, { (spriteXvel * (sintable[(angDiff + 512) & 2047])) >> 14,
                                                          (spriteXvel * (sintable[angDiff & 2047])) >> 14, vm.pSprite->zvel },
                                                        A_CheckSpriteFlags(vm.spriteNum, SFLAG_NOCLIP) ? 0 : CLIPMASK0);
    }

    if (!badguyp)
        return;

    vm.pSprite->shade += (sector[vm.pSprite->sectnum].ceilingstat & 1) ? (sector[vm.pSprite->sectnum].ceilingshade - vm.pSprite->shade) >> 1
                                                                 : (sector[vm.pSprite->sectnum].floorshade - vm.pSprite->shade) >> 1;
}

static void P_AddWeaponMaybeSwitch(DukePlayer_t * const ps, int const weaponNum)
{
    if ((ps->weaponswitch & (1|4)) == (1|4))
    {
        int const playerNum    = P_Get(ps->i);
        int       new_wchoice  = -1;
        int       curr_wchoice = -1;

        for (native_t i=0; i<=FREEZE_WEAPON && ((new_wchoice < 0) | (curr_wchoice < 0)); i++)
        {
            int w = g_player[playerNum].wchoice[i];

            if (w == KNEE_WEAPON)
                w = FREEZE_WEAPON;
            else
                w--;

            if (w == ps->curr_weapon)
                curr_wchoice = i;
            if (w == weaponNum)
                new_wchoice = i;
        }

        P_AddWeapon(ps, weaponNum, (new_wchoice < curr_wchoice));
    }
    else
    {
        P_AddWeapon(ps, weaponNum, (ps->weaponswitch & 1));
    }
}

static void P_AddWeaponAmmoCommon(DukePlayer_t * const pPlayer, int const weaponNum, int const nAmount)
{
    P_AddAmmo(pPlayer, weaponNum, nAmount);

    if (PWEAPON(vm.playerNum, pPlayer->curr_weapon, WorksLike) == KNEE_WEAPON && (pPlayer->gotweapon & (1 << weaponNum)))
        P_AddWeaponMaybeSwitch(pPlayer, weaponNum);
}

static void VM_AddWeapon(DukePlayer_t * const pPlayer, int const weaponNum, int const nAmount)
{
    if (EDUKE32_PREDICT_FALSE((unsigned)weaponNum >= MAX_WEAPONS))
    {
        CON_ERRPRINTF("invalid weapon %d", weaponNum);
        return;
    }

    if ((pPlayer->gotweapon & (1 << weaponNum)) == 0)
    {
        P_AddWeaponMaybeSwitch(pPlayer, weaponNum);
    }
    else if (pPlayer->ammo_amount[weaponNum] >= pPlayer->max_ammo_amount[weaponNum])
    {
        vm.flags |= VM_NOEXECUTE;
        return;
    }

    P_AddWeaponAmmoCommon(pPlayer, weaponNum, nAmount);
}

static void VM_AddAmmo(DukePlayer_t * const pPlayer, int const weaponNum, int const nAmount)
{
    if (EDUKE32_PREDICT_FALSE((unsigned)weaponNum >= MAX_WEAPONS))
    {
        CON_ERRPRINTF("invalid weapon %d", weaponNum);
        return;
    }

    if (pPlayer->ammo_amount[weaponNum] >= pPlayer->max_ammo_amount[weaponNum])
    {
        vm.flags |= VM_NOEXECUTE;
        return;
    }

    P_AddWeaponAmmoCommon(pPlayer, weaponNum, nAmount);
}

static void VM_AddInventory(DukePlayer_t * const pPlayer, int const itemNum, int const nAmount)
{
    switch (itemNum)
    {
    case GET_STEROIDS:
    case GET_SCUBA:
    case GET_HOLODUKE:
    case GET_JETPACK:
    case GET_HEATS:
    case GET_FIRSTAID:
    case GET_BOOTS:
        pPlayer->inven_icon = inv_to_icon[itemNum];
        pPlayer->inv_amount[itemNum] = nAmount;
        break;

    case GET_SHIELD:
    {
        int16_t & shield_amount = pPlayer->inv_amount[GET_SHIELD];
        shield_amount = min(shield_amount + nAmount, pPlayer->max_shield_amount);
        break;
    }

    case GET_ACCESS:
        switch (vm.pSprite->pal)
        {
                case 0: pPlayer->got_access |= 1; break;
                case 21: pPlayer->got_access |= 2; break;
                case 23: pPlayer->got_access |= 4; break;
        }
        break;

        default: CON_ERRPRINTF("invalid inventory item %d", itemNum); break;
    }
}

static int A_GetVerticalVel(actor_t const * const pActor)
{
    int32_t moveScriptOfs = AC_MOVE_ID(pActor->t_data);

    return ((unsigned) moveScriptOfs < (unsigned) g_scriptSize - 1) ? apScript[moveScriptOfs + 1] : 0;
}

static int32_t A_GetWaterZOffset(int const spriteNum)
{
    auto const pSprite = (uspriteptr_t)&sprite[spriteNum];
    auto const pActor  = &actor[spriteNum];

    if (sector[pSprite->sectnum].lotag == ST_1_ABOVE_WATER)
    {
        if (A_CheckSpriteFlags(spriteNum, SFLAG_NOWATERDIP))
            return 0;

        // fix for flying/jumping monsters getting stuck in water
        if ((AC_MOVFLAGS(pSprite, pActor) & jumptoplayer_only) || (G_TileHasActor(pSprite->picnum) && A_GetVerticalVel(pActor) != 0))
            return 0;

        return ACTOR_ONWATER_ADDZ - ((int32_t) pActor->waterzoffset << 8);
    }

    return 0;
}

static void VM_Fall(int const spriteNum, spritetype * const pSprite)
{
    MICROPROFILE_SCOPEI("VM", EDUKE32_FUNCTION, MP_AUTO);

    int spriteGravity = g_spriteGravity;

    pSprite->xoffset = pSprite->yoffset = 0;

    if (sector[pSprite->sectnum].lotag == ST_2_UNDERWATER || EDUKE32_PREDICT_FALSE(G_CheckForSpaceCeiling(pSprite->sectnum)))
        spriteGravity = g_spriteGravity/6;
    else if (EDUKE32_PREDICT_FALSE(G_CheckForSpaceFloor(pSprite->sectnum)))
        spriteGravity = 0;

    if (!actor[spriteNum].cgg-- || (sector[pSprite->sectnum].floorstat&2))
        actor[spriteNum].cgg = 3;

    A_GetZLimits(spriteNum);

    if (pSprite->z < actor[spriteNum].floorz - AC_FZOFFSET(spriteNum))
    {
        // Free fall.
        pSprite->zvel = min(pSprite->zvel+spriteGravity, ACTOR_MAXFALLINGZVEL);
        int newZ = pSprite->z + pSprite->zvel;

#ifdef YAX_ENABLE
        if (yax_getbunch(pSprite->sectnum, YAX_FLOOR) >= 0 && (sector[pSprite->sectnum].floorstat & 512) == 0)
            setspritez(spriteNum, &pSprite->xyz);
        else
#endif
            if (newZ > actor[spriteNum].floorz - AC_FZOFFSET(spriteNum))
                newZ = actor[spriteNum].floorz - AC_FZOFFSET(spriteNum);

        pSprite->z = newZ;
        return;
    }

    // Preliminary new z position of the actor.
    int newZ = actor[spriteNum].floorz - AC_FZOFFSET(spriteNum);

    if (A_CheckEnemySprite(pSprite) || (pSprite->picnum == APLAYER && pSprite->owner >= 0))
    {
        if (pSprite->zvel > 3084 && pSprite->extra <= 1)
        {
            // I'm guessing this DRONE check is from a beta version of the game
            // where they crashed into the ground when killed
#ifndef EDUKE32_STANDALONE
            if (!FURY && !(pSprite->picnum == APLAYER && pSprite->extra > 0) && pSprite->pal != 1 && pSprite->picnum != DRONE)
            {
                A_DoGuts(spriteNum,JIBS6,15);
                A_PlaySound(SQUISHED,spriteNum);
                A_Spawn(spriteNum,BLOODPOOL);
            }
#endif
            actor[spriteNum].htpicnum = SHOTSPARK1;
            actor[spriteNum].htextra = 1;
            pSprite->zvel = 0;
        }
        else if (pSprite->zvel > 2048 && sector[pSprite->sectnum].lotag != ST_1_ABOVE_WATER)
        {
            int16_t newsect = pSprite->sectnum;

            pushmove(&pSprite->xyz, &newsect, 128, 4<<8, 4<<8, CLIPMASK0);
            if ((unsigned)newsect < MAXSECTORS)
                changespritesect(spriteNum, newsect);

#ifndef EDUKE32_STANDALONE
            if (!FURY)
                A_PlaySound(THUD, spriteNum);
#endif
        }
    }

    if (sector[pSprite->sectnum].lotag == ST_1_ABOVE_WATER && actor[spriteNum].floorz == yax_getflorzofslope(pSprite->sectnum, pSprite->xy))
    {
        pSprite->z = newZ + A_GetWaterZOffset(spriteNum);
        return;
    }

    pSprite->z = newZ;
    pSprite->zvel = 0;
}

static int32_t VM_ResetPlayer(int const playerNum, int32_t vmFlags, int32_t const resetFlags)
{
    //AddLog("resetplayer");
    if (!g_netServer && ud.multimode < 2 && !(resetFlags & 2))
    {
        if (g_quickload && g_quickload->isValid() && ud.recstat != 2 && !(resetFlags & 8))
        {
            if (resetFlags & 4)
            {
                KB_FlushKeyboardQueue();
                KB_ClearKeysDown();
                FX_StopAllSounds();
                S_ClearSoundLocks();
                if (G_LoadPlayerMaybeMulti(*g_quickload) != 0)
                {
                    g_quickload->reset();
                    goto QuickLoadFailure;
                }
            }
            else if (!(resetFlags & 1))
            {
                Menu_Open(playerNum);
                KB_ClearKeyDown(sc_Space);
                I_AdvanceTriggerClear();
                Menu_Change(MENU_RESETPLAYER);
            }
        }
        else
        {
            QuickLoadFailure:
            g_player[playerNum].ps->gm = MODE_RESTART;
        }
        vmFlags |= VM_NOEXECUTE;
    }
    else
    {
        if (playerNum == myconnectindex)
        {
            CAMERADIST = 0;
            CAMERACLOCK = (int32_t) totalclock;
        }

        if (g_fakeMultiMode)
            P_ResetMultiPlayer(playerNum);
#ifndef NETCODE_DISABLE
        if (g_netServer)
        {
            P_ResetMultiPlayer(playerNum);
            Net_SpawnPlayer(playerNum);
        }
#endif
    }

    P_UpdateScreenPal(g_player[playerNum].ps);
    //AddLog("EOF: resetplayer");

    return vmFlags;
}

void G_GetTimeDate(int32_t * const pValues)
{
    time_t timeStruct;
    time(&timeStruct);
    struct tm *pTime = localtime(&timeStruct);

    // initprintf("Time&date: %s\n",asctime (ti));

    pValues[0] = pTime->tm_sec;
    pValues[1] = pTime->tm_min;
    pValues[2] = pTime->tm_hour;
    pValues[3] = pTime->tm_mday;
    pValues[4] = pTime->tm_mon;
    pValues[5] = pTime->tm_year+1900;
    pValues[6] = pTime->tm_wday;
    pValues[7] = pTime->tm_yday;
}

static int G_StartTrackSlot(int const volumeNum, int const levelNum)
{
    if ((unsigned)volumeNum <= MAXVOLUMES && (unsigned)levelNum < MAXLEVELS)
    {
        int trackNum = MAXLEVELS*volumeNum + levelNum;

        return S_TryPlaySpecialMusic(trackNum);
    }

    return 1;
}

static int G_StartTrackSlotWrap(int const volumeNum, int const levelNum)
{
    if (EDUKE32_PREDICT_FALSE(G_StartTrackSlot(volumeNum, levelNum)))
    {
        CON_ERRPRINTF("invalid level %d or null music for volume %d level %d", levelNum, volumeNum, levelNum);
        return 1;
    }

    return 0;
}

static void G_ShowView(vec3_t vec, fix16_t a, fix16_t horiz, int sect, int ix1, int iy1, int ix2, int iy2, int unbiasedp)
{
    int x1 = min(ix1, ix2);
    int x2 = max(ix1, ix2);
    int y1 = min(iy1, iy2);
    int y2 = max(iy1, iy2);

    if (!unbiasedp)
    {
        // The showview command has a rounding bias towards zero,
        // e.g. floor((319*1680)/320) == 1674
        x1 = scale(x1,xdim,320);
        y1 = scale(y1,ydim,200);
        x2 = scale(x2,xdim,320);
        y2 = scale(y2,ydim,200);
    }
    else
    {
        // This will map the maximum 320-based coordinate to the
        // maximum real screen coordinate:
        // floor((319*1679)/319) == 1679
        x1 = scale(x1,xdim-1,319);
        y1 = scale(y1,ydim-1,199);
        x2 = scale(x2,xdim-1,319);
        y2 = scale(y2,ydim-1,199);
    }

    horiz = fix16_clamp(horiz, F16(HORIZ_MIN), F16(HORIZ_MAX));

    int const viewingRange = viewingrange;
    int const yxAspect = yxaspect;

    videoSetViewableArea(x1,y1,x2,y2);
    renderSetAspect(viewingRange, yxAspect);
    int const smoothratio = calc_smoothratio(totalclock, ototalclock);
    G_DoInterpolations(smoothratio);
    if (!display_mirror)
        G_HandleMirror(vec.x, vec.y, vec.z, a, horiz, smoothratio);
#ifdef POLYMER
    if (videoGetRenderMode() == REND_POLYMER)
        polymer_setanimatesprites(G_DoSpriteAnimations, vec.x, vec.y, vec.z, fix16_to_int(a), smoothratio);
#endif
    yax_preparedrawrooms();
    renderDrawRoomsQ16(vec.x, vec.y, vec.z, a, horiz, sect);
    yax_drawrooms(G_DoSpriteAnimations, sect, 0, smoothratio);

    display_mirror = 2;
    G_DoSpriteAnimations(vec.x, vec.y, vec.z, fix16_to_int(a), smoothratio);
    display_mirror = 0;
    renderDrawMasks();
    G_RestoreInterpolations();
    G_UpdateScreenArea();
    renderSetAspect(viewingRange, yxAspect);
}

void Screen_Play(void)
{
    bool running = true;
    int const gm = g_player[myconnectindex].ps->gm;

    g_player[myconnectindex].ps->gm &= ~MODE_MENU;

    I_ClearAllInput();

    do
    {
        gameHandleEvents();

        ototalclock = totalclock + 1; // pause game like ANMs
        m_mouselastactivity = (int)totalclock;

        if (!engineFPSLimit(true))
            continue;

        videoClearScreen(0);

        if (VM_OnEventWithReturn(EVENT_SCREEN, -1, myconnectindex, I_CheckAllInput()))
            running = false;

        videoNextPage();
    } while (running);

    g_player[myconnectindex].ps->gm = gm;

    I_ClearAllInput();
}

static inline void SetArray(int const arrayNum, int const arrayIndex, int const newValue)
{
    auto &arr = aGameArrays[arrayNum];

#if 0 // this needs to be a compile time check, not something done at runtime...
    if (EDUKE32_PREDICT_FALSE(arr.flags & GAMEARRAY_READONLY))
    {
        LOG_F(ERROR, "Tried to set value in read-only array '%s'.", arr.szLabel);
        vm.flags |= VM_RETURN;
        return;
    }
#endif

    switch (arr.flags & GAMEARRAY_TYPE_MASK)
    {
        case 0: arr.pValues[arrayIndex]                              = newValue; break;
        case GAMEARRAY_INT16: ((int16_t *)arr.pValues)[arrayIndex]   = newValue; break;
        case GAMEARRAY_INT8: ((int8_t *)arr.pValues)[arrayIndex]     = newValue; break;
        case GAMEARRAY_UINT16: ((uint16_t *)arr.pValues)[arrayIndex] = newValue; break;
        case GAMEARRAY_UINT8: ((int8_t *)arr.pValues)[arrayIndex]    = newValue; break;
        case GAMEARRAY_BITMAP:
        {
            uint32_t const mask  = pow2char[arrayIndex&7];
            uint8_t &value = ((uint8_t *)arr.pValues)[arrayIndex>>3];
            value = (value & ~mask) | (-!!newValue & mask);
            break;
        }
    }
}

static void ResizeArray(int const arrayNum, int const newSize)
{
    auto &arr = aGameArrays[arrayNum];

    int const oldSize = arr.size;

    if ((newSize == oldSize) | (newSize < 0))
        return;
#if 0
    OSD_Printf(OSDTEXT_GREEN "CON_RESIZEARRAY: resizing array %s from %d to %d\n",
               array.szLabel, array.size, newSize);
#endif
    if (newSize == 0)
    {
        Xaligned_free(arr.pValues);
        arr.pValues = nullptr;
        arr.size = 0;
        return;
    }

    size_t const oldBytes = Gv_GetArrayAllocSizeForCount(arrayNum, oldSize);
    size_t const newBytes = Gv_GetArrayAllocSizeForCount(arrayNum, newSize);

    auto const oldArray = arr.pValues;
    auto const newArray = (intptr_t *)Xaligned_alloc(ARRAY_ALIGNMENT, newBytes);

    if (oldSize != 0)
        Bmemcpy(newArray, oldArray, min(oldBytes, newBytes));

    if (newSize > oldSize)
        Bmemset((char *)newArray + oldBytes, 0, newBytes - oldBytes);

    arr.pValues = newArray;
    arr.size = newSize;

    Xaligned_free(oldArray);
}

#if defined __GNUC__ || defined __clang__
# define CON_USE_COMPUTED_GOTO
#endif

#ifdef CON_USE_COMPUTED_GOTO
# define vInstruction(KEYWORDID) VINST_ ## KEYWORDID
# define vmErrorCase VINST_CON_OPCODE_END
# define eval(INSTRUCTION) { goto *jumpTable[INSTRUCTION]; }
# define dispatch_unconditionally(...) { eval((VM_DECODE_INST((g_tw = tw = *insptr)))) }
# define dispatch(...) { if (!vm_execution_depth | ((vm.flags & (VM_RETURN|VM_KILL|VM_NOEXECUTE)) != 0)) return; dispatch_unconditionally(__VA_ARGS__); }
# define abort_after_error(...) return
# define vInstructionPointer(KEYWORDID) &&VINST_ ## KEYWORDID
# define COMMA ,
# define JUMP_TABLE_ARRAY_LITERAL { TRANSFORM_SCRIPT_KEYWORDS_LIST(vInstructionPointer, COMMA) }
#else
# define vInstruction(KEYWORDID) case KEYWORDID
# define vmErrorCase default
# define dispatch_unconditionally(...) continue
# define dispatch(...) continue
# define eval(INSTRUCTION) switch(INSTRUCTION)
# define abort_after_error(...) continue // non-threaded dispatch handles this in the loop condition in VM_Execute()
#endif

#if defined _MSC_VER
#define VM_ABORT_IF(condition, fmt, ...)  \
    if (EDUKE32_PREDICT_FALSE(condition)) \
    {                                     \
        CON_ERRPRINTF(fmt, __VA_ARGS__);  \
        abort_after_error();              \
    }
#else
#define VM_ABORT_IF(condition, ...)       \
    if (EDUKE32_PREDICT_FALSE(condition)) \
    {                                     \
        CON_ERRPRINTF(__VA_ARGS__);       \
        abort_after_error();              \
    }
#endif

GAMEEXEC_STATIC void VM_Execute(int vm_execution_depth /*= false*/)
{
    auto branch = [&](int const x)
    {
        if (x || VM_DECODE_INST(*(insptr = (intptr_t *)insptr[1])) == CON_ELSE)
        {
            insptr += 2;
            VM_Execute();
        }
    };

    auto bad_quote = [](int const q)
    {
        return ((unsigned)q >= MAXQUOTES) | (apStrings[q & (MAXQUOTES-1)] == nullptr);
    };

#ifdef CON_USE_COMPUTED_GOTO
    static void *const jumpTable[] = JUMP_TABLE_ARRAY_LITERAL;
#else
    do
    {
#endif
        int32_t tw = *insptr;
        g_tw = tw;

        int const decoded = VM_DECODE_INST(tw);
#if 0 && defined CON_USE_COMPUTED_GOTO
        // this is broken without CON_USE_COMPUTED_GOTO because it never goes out of scope
        MICROPROFILE_SCOPE_TOKEN(g_instTokens[decoded]);
#endif
        eval(decoded)
        {
            vInstruction(CON_LEFTBRACE):
            {
                insptr++, vm_execution_depth++;
                dispatch_unconditionally();
            }

            vInstruction(CON_RIGHTBRACE):
            {
                insptr++, vm_execution_depth--;
                dispatch();
            }

            vInstruction(CON_ELSE):
            {
                insptr = (intptr_t *)insptr[1];
                dispatch_unconditionally();
            }

            vInstruction(CON_STATE):
            {
                auto tempscrptr = &insptr[2];
                insptr = (intptr_t *)insptr[1];
                VM_Execute(true);
                insptr = tempscrptr;
            }
            dispatch();

            vInstruction(CON_SETVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global = insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SETVAR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] = insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SETVAR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] = insptr[1];
                insptr += 2;
                dispatch();

#ifdef CON_DISCRETE_VAR_ACCESS
            vInstruction(CON_IFVARE_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw == *insptr);
                dispatch();
            vInstruction(CON_IFVARN_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw != *insptr);
                dispatch();
            vInstruction(CON_IFVARAND_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw & *insptr);
                dispatch();
            vInstruction(CON_IFVAROR_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw | *insptr);
                dispatch();
            vInstruction(CON_IFVARXOR_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw ^ *insptr);
                dispatch();
            vInstruction(CON_IFVAREITHER_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw || *insptr);
                dispatch();
            vInstruction(CON_IFVARBOTH_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw && *insptr);
                dispatch();
            vInstruction(CON_IFVARG_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw > *insptr);
                dispatch();
            vInstruction(CON_IFVARGE_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw >= *insptr);
                dispatch();
            vInstruction(CON_IFVARL_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw < *insptr);
                dispatch();
            vInstruction(CON_IFVARLE_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch(tw <= *insptr);
                dispatch();
            vInstruction(CON_IFVARA_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch((uint32_t)tw > (uint32_t)*insptr);
                dispatch();
            vInstruction(CON_IFVARAE_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch((uint32_t)tw >= (uint32_t)*insptr);
                dispatch();
            vInstruction(CON_IFVARB_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch((uint32_t)tw < (uint32_t)*insptr);
                dispatch();
            vInstruction(CON_IFVARBE_GLOBAL):
                insptr++;
                tw = aGameVars[*insptr++].global;
                branch((uint32_t)tw <= (uint32_t)*insptr);
                dispatch();

            vInstruction(CON_ADDVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global += insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SUBVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global -= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_MULVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global *= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_ANDVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global &= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_XORVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global ^= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_ORVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global |= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SHIFTVARL_GLOBAL):
                insptr++;
                aGameVars[*insptr].global <<= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SHIFTVARR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global >>= insptr[1];
                insptr += 2;
                dispatch();

            vInstruction(CON_IFVARE_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw == *insptr);
                dispatch();
            vInstruction(CON_IFVARN_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw != *insptr);
                dispatch();
            vInstruction(CON_IFVARAND_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw & *insptr);
                dispatch();
            vInstruction(CON_IFVAROR_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw | *insptr);
                dispatch();
            vInstruction(CON_IFVARXOR_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw ^ *insptr);
                dispatch();
            vInstruction(CON_IFVAREITHER_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw || *insptr);
                dispatch();
            vInstruction(CON_IFVARBOTH_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw && *insptr);
                dispatch();
            vInstruction(CON_IFVARG_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw > *insptr);
                dispatch();
            vInstruction(CON_IFVARGE_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw >= *insptr);
                dispatch();
            vInstruction(CON_IFVARL_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw < *insptr);
                dispatch();
            vInstruction(CON_IFVARLE_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch(tw <= *insptr);
                dispatch();
            vInstruction(CON_IFVARA_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch((uint32_t)tw > (uint32_t)*insptr);
                dispatch();
            vInstruction(CON_IFVARAE_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch((uint32_t)tw >= (uint32_t)*insptr);
                dispatch();
            vInstruction(CON_IFVARB_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch((uint32_t)tw < (uint32_t)*insptr);
                dispatch();
            vInstruction(CON_IFVARBE_ACTOR):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.spriteNum & (MAXSPRITES-1)];
                branch((uint32_t)tw <= (uint32_t)*insptr);
                dispatch();

            vInstruction(CON_ADDVAR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] += insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SUBVAR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] -= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_MULVAR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] *= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_ANDVAR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] &= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_XORVAR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] ^= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_ORVAR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] |= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SHIFTVARL_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] <<= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SHIFTVARR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] >>= insptr[1];
                insptr += 2;
                dispatch();

            vInstruction(CON_IFVARE_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw == *insptr);
                dispatch();
            vInstruction(CON_IFVARN_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw != *insptr);
                dispatch();
            vInstruction(CON_IFVARAND_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw & *insptr);
                dispatch();
            vInstruction(CON_IFVAROR_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw | *insptr);
                dispatch();
            vInstruction(CON_IFVARXOR_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw ^ *insptr);
                dispatch();
            vInstruction(CON_IFVAREITHER_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw || *insptr);
                dispatch();
            vInstruction(CON_IFVARBOTH_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw && *insptr);
                dispatch();
            vInstruction(CON_IFVARG_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw > *insptr);
                dispatch();
            vInstruction(CON_IFVARGE_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw >= *insptr);
                dispatch();
            vInstruction(CON_IFVARL_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw < *insptr);
                dispatch();
            vInstruction(CON_IFVARLE_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch(tw <= *insptr);
                dispatch();
            vInstruction(CON_IFVARA_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch((uint32_t)tw > (uint32_t)*insptr);
                dispatch();
            vInstruction(CON_IFVARAE_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch((uint32_t)tw >= (uint32_t)*insptr);
                dispatch();
            vInstruction(CON_IFVARB_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch((uint32_t)tw < (uint32_t)*insptr);
                dispatch();
            vInstruction(CON_IFVARBE_PLAYER):
                insptr++;
                tw = aGameVars[*insptr++].pValues[vm.playerNum & (MAXPLAYERS-1)];
                branch((uint32_t)tw <= (uint32_t)*insptr);
                dispatch();

            vInstruction(CON_ADDVAR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] += insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SUBVAR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] -= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_MULVAR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] *= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_ANDVAR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] &= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_XORVAR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] ^= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_ORVAR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] |= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SHIFTVARL_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] <<= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_SHIFTVARR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] >>= insptr[1];
                insptr += 2;
                dispatch();

            vInstruction(CON_WHILEVARN_GLOBAL):
            {
                auto const savedinsptr = &insptr[2];
                do
                {
                    insptr = savedinsptr;
                    tw = (aGameVars[insptr[-1]].global != *insptr);
                    branch(tw);
                } while (tw && (vm.flags & VM_RETURN) == 0);
                dispatch();
            }

            vInstruction(CON_WHILEVARL_GLOBAL):
            {
                auto const savedinsptr = &insptr[2];
                do
                {
                    insptr = savedinsptr;
                    tw = (aGameVars[insptr[-1]].global < *insptr);
                    branch(tw);
                } while (tw && (vm.flags & VM_RETURN) == 0);
                dispatch();
            }

            vInstruction(CON_WHILEVARN_ACTOR):
            {
                auto const savedinsptr = &insptr[2];
                auto &v = aGameVars[savedinsptr[-1]].pValues[vm.spriteNum & (MAXSPRITES-1)];
                do
                {
                    insptr = savedinsptr;
                    tw = (v != *insptr);
                    branch(tw);
                } while (tw && (vm.flags & VM_RETURN) == 0);

                dispatch();
            }

            vInstruction(CON_WHILEVARL_ACTOR):
            {
                auto const savedinsptr = &insptr[2];
                auto &v = aGameVars[savedinsptr[-1]].pValues[vm.spriteNum & (MAXSPRITES-1)];
                do
                {
                    insptr = savedinsptr;
                    tw = (v < *insptr);
                    branch(tw);
                } while (tw && (vm.flags & VM_RETURN) == 0);

                dispatch();
            }

            vInstruction(CON_WHILEVARN_PLAYER):
            {
                auto const savedinsptr = &insptr[2];
                auto &v = aGameVars[savedinsptr[-1]].pValues[vm.playerNum & (MAXPLAYERS-1)];
                do
                {
                    insptr = savedinsptr;
                    tw = (v != *insptr);
                    branch(tw);
                } while (tw && (vm.flags & VM_RETURN) == 0);

                dispatch();
            }

            vInstruction(CON_WHILEVARL_PLAYER):
            {
                auto const savedinsptr = &insptr[2];
                auto &v = aGameVars[savedinsptr[-1]].pValues[vm.playerNum & (MAXPLAYERS-1)];
                do
                {
                    insptr = savedinsptr;
                    tw = (v < *insptr);
                    branch(tw);
                } while (tw && (vm.flags & VM_RETURN) == 0);

                dispatch();
            }

            vInstruction(CON_MODVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global %= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_MODVAR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] %= insptr[1];
                insptr += 2;
                dispatch();
            vInstruction(CON_MODVAR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] %= insptr[1];
                insptr += 2;
                dispatch();
#endif

            vInstruction(CON_IFVARAND):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw & *insptr);
                dispatch();

            vInstruction(CON_IFVAROR):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw | *insptr);
                dispatch();

            vInstruction(CON_IFVARXOR):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw ^ *insptr);
                dispatch();

            vInstruction(CON_IFVAREITHER):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw || *insptr);
                dispatch();

            vInstruction(CON_IFVARBOTH):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw && *insptr);
                dispatch();

            vInstruction(CON_IFRND):
                branch(rnd(*(++insptr)));
                dispatch();

            vInstruction(CON_IFVARG):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw > *insptr);
                dispatch();

            vInstruction(CON_IFVARGE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw >= *insptr);
                dispatch();

            vInstruction(CON_IFVARL):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw < *insptr);
                dispatch();

            vInstruction(CON_IFVARLE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw <= *insptr);
                dispatch();

            vInstruction(CON_IFVARA):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch((uint32_t)tw > (uint32_t)*insptr);
                dispatch();

            vInstruction(CON_IFVARAE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch((uint32_t)tw >= (uint32_t)*insptr);
                dispatch();

            vInstruction(CON_IFVARB):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch((uint32_t)tw < (uint32_t)*insptr);
                dispatch();

            vInstruction(CON_IFVARBE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch((uint32_t)tw <= (uint32_t)*insptr);
                dispatch();

            vInstruction(CON_SETVARVAR):
                insptr++;
                {
                    tw = *insptr++;
                    int const nValue = Gv_GetVar(*insptr++);

                    if ((aGameVars[tw].flags & (GAMEVAR_USER_MASK | GAMEVAR_PTR_MASK)) == 0)
                        aGameVars[tw].global = nValue;
                    else
                        Gv_SetVar(tw, nValue);
                }
                dispatch();

            vInstruction(CON_ADDVARVAR):
                insptr++;
                tw = *insptr++;
                Gv_AddVar(tw, Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_SUBVARVAR):
                insptr++;
                tw = *insptr++;
                Gv_SubVar(tw, Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_ANDVARVAR):
                insptr++;
                tw = *insptr++;
                Gv_AndVar(tw, Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_XORVARVAR):
                insptr++;
                tw = *insptr++;
                Gv_XorVar(tw, Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_ORVARVAR):
                insptr++;
                tw = *insptr++;
                Gv_OrVar(tw, Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_SHIFTVARVARL):
                insptr++;
                tw = *insptr++;
                Gv_ShiftVarL(tw, Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_SHIFTVARVARR):
                insptr++;
                tw = *insptr++;
                Gv_ShiftVarR(tw, Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_MULVARVAR):
                insptr++;
                tw = *insptr++;
                Gv_MulVar(tw, Gv_GetVar(*insptr++));
                dispatch();

#ifdef CON_DISCRETE_VAR_ACCESS
            vInstruction(CON_DIVVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global = tabledivide32(aGameVars[*insptr].global, insptr[1]);
                insptr += 2;
                dispatch();

            vInstruction(CON_DIVVAR_PLAYER):
            {
                insptr++;
                auto &v = aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS - 1)];

                v = tabledivide32(v, insptr[1]);
                insptr += 2;
                dispatch();
            }

            vInstruction(CON_DIVVAR_ACTOR):
            {
                insptr++;
                auto &v = aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES - 1)];

                v = tabledivide32(v, insptr[1]);
                insptr += 2;
                dispatch();
            }
#endif

            vInstruction(CON_DIVVARVAR):
                insptr++;
                {
                    tw = *insptr++;

                    int const nValue = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(!nValue, "divide by zero!");

                    Gv_DivVar(tw, nValue);
                    dispatch();
                }

            vInstruction(CON_IFVARE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw == *insptr);
                dispatch();

            vInstruction(CON_IFVARN):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                branch(tw != *insptr);
                dispatch();

            vInstruction(CON_IFVARVARE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = (tw == Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARN):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = (tw != Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARG):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = (tw > Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARGE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = (tw >= Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARL):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = (tw < Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARLE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = (tw <= Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARA):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = ((uint32_t)tw > (uint32_t)Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARAE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = ((uint32_t)tw >= (uint32_t)Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARB):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = ((uint32_t)tw < (uint32_t)Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARBE):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = ((uint32_t)tw <= (uint32_t)Gv_GetVar(*insptr++));
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARAND):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw &= Gv_GetVar(*insptr++);
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVAROR):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw |= Gv_GetVar(*insptr++);
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARXOR):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw ^= Gv_GetVar(*insptr++);
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVAREITHER):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = (Gv_GetVar(*insptr++) || tw);
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_IFVARVARBOTH):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                tw = (Gv_GetVar(*insptr++) && tw);
                insptr--;
                branch(tw);
                dispatch();

            vInstruction(CON_WHILEVARN):
            {
                auto const savedinsptr = &insptr[2];
                do
                {
                    insptr = savedinsptr;
                    branch((tw = (Gv_GetVar(insptr[-1]) != *insptr)));
                } while (tw && (vm.flags & VM_RETURN) == 0);
                dispatch();
            }

            vInstruction(CON_WHILEVARVARN):
            {
                auto const savedinsptr = &insptr[2];
                do
                {
                    insptr = savedinsptr;
                    tw = Gv_GetVar(insptr[-1]);
                    tw = (tw != Gv_GetVar(*insptr++));
                    insptr--;
                    branch(tw);
                } while (tw && (vm.flags & VM_RETURN) == 0);
                dispatch();
            }

            vInstruction(CON_WHILEVARL):
            {
                auto const savedinsptr = &insptr[2];
                do
                {
                    insptr = savedinsptr;
                    branch((tw = (Gv_GetVar(insptr[-1]) < *insptr)));
                } while (tw && (vm.flags & VM_RETURN) == 0);
                dispatch();
            }

            vInstruction(CON_WHILEVARVARL):
            {
                auto const savedinsptr = &insptr[2];
                do
                {
                    insptr = savedinsptr;
                    tw = Gv_GetVar(insptr[-1]);
                    tw = (tw < Gv_GetVar(*insptr++));
                    insptr--;
                    branch(tw);
                } while (tw && (vm.flags & VM_RETURN) == 0);
                dispatch();
            }

            vInstruction(CON_SETVAR):
                Gv_SetVar(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_ADDVAR):
                Gv_AddVar(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_SUBVAR):
                Gv_SubVar(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_MULVAR):
                Gv_MulVar(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_DIVVAR):
                Gv_DivVar(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_ANDVAR):
                Gv_AndVar(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_XORVAR):
                Gv_XorVar(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_ORVAR):
                Gv_OrVar(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_SHIFTVARL):
                Gv_ShiftVarL(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_SHIFTVARR):
                Gv_ShiftVarR(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_MODVAR):
                Gv_ModVar(insptr[1], insptr[2]);
                insptr += 3;
                dispatch();

            vInstruction(CON_MODVARVAR):
                insptr++;
                {
                    tw = *insptr++;

                    int const nValue = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(!nValue, "mod by zero!");

                    Gv_ModVar(tw, nValue);
                    dispatch();
                }

            vInstruction(CON_RANDVAR):
                insptr++;
                Gv_SetVar(*insptr, mulscale16(krand(), insptr[1] + 1));
                insptr += 2;
                dispatch();

#ifdef CON_DISCRETE_VAR_ACCESS
            vInstruction(CON_RANDVAR_GLOBAL):
                insptr++;
                aGameVars[*insptr].global = mulscale16(krand(), insptr[1] + 1);
                insptr += 2;
                dispatch();

            vInstruction(CON_RANDVAR_PLAYER):
                insptr++;
                aGameVars[*insptr].pValues[vm.playerNum & (MAXPLAYERS-1)] = mulscale16(krand(), insptr[1] + 1);
                insptr += 2;
                dispatch();

            vInstruction(CON_RANDVAR_ACTOR):
                insptr++;
                aGameVars[*insptr].pValues[vm.spriteNum & (MAXSPRITES-1)] = mulscale16(krand(), insptr[1] + 1);
                insptr += 2;
                dispatch();
#endif

            vInstruction(CON_RANDVARVAR):
                insptr++;
                tw = *insptr++;
                Gv_SetVar(tw, mulscale16(krand(), Gv_GetVar(*insptr++) + 1));
                dispatch();

            vInstruction(CON_SETPLAYER):
                insptr++;
                {
                    int const   playerNum   = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.playerNum;
                    auto const &playerLabel = PlayerLabels[(tw = *insptr++)];
                    int const   lParm2      = (playerLabel.flags & LABEL_HASPARM2) ? Gv_GetVar(*insptr++) : 0;

                    VM_ABORT_IF(((unsigned)playerNum >= MAXSPRITES) | ((unsigned)lParm2 > (unsigned)playerLabel.maxParm2),
                              "%s[%d] invalid for player %d", playerLabel.name, lParm2, playerNum);

                    VM_SetPlayer(playerNum, tw, lParm2, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETPLAYER):
                insptr++;
                {
                    int const   playerNum   = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.playerNum;
                    auto const &playerLabel = PlayerLabels[(tw = *insptr++)];
                    int const   lParm2      = (playerLabel.flags & LABEL_HASPARM2) ? Gv_GetVar(*insptr++) : 0;

                    VM_ABORT_IF(((unsigned)playerNum >= MAXSPRITES) | ((unsigned)lParm2 > (unsigned)playerLabel.maxParm2),
                              "%s[%d] invalid for player %d", playerLabel.name, lParm2, playerNum);

                    Gv_SetVar(*insptr++, VM_GetPlayer(playerNum, tw, lParm2));
                    dispatch();
                }

            vInstruction(CON_SETPLAYERSTRUCT):
                insptr++;
                {
                    int const playerNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.playerNum;
                    VM_ABORT_IF((unsigned)playerNum >= MAXPLAYERS, "invalid player %d", playerNum);
                    auto const &playerLabel = PlayerLabels[*insptr++];

                    VM_SetStruct(playerLabel.flags, (intptr_t *)((char *)&g_player[playerNum].ps[0] + playerLabel.offset), Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETPLAYERSTRUCT):
                insptr++;
                {
                    int const playerNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.playerNum;
                    VM_ABORT_IF((unsigned)playerNum >= MAXPLAYERS, "invalid player %d", playerNum);
                    auto const &playerLabel = PlayerLabels[*insptr++];

                    Gv_SetVar(*insptr++, VM_GetStruct(playerLabel.flags, (intptr_t *)((char *)&g_player[playerNum].ps[0] + playerLabel.offset)));
                    dispatch();
                }

            vInstruction(CON_SETWALL):
                insptr++;
                {
                    int const wallNum  = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)wallNum >= MAXWALLS, "invalid wall %d", wallNum);

                    tw = *insptr++;

                    VM_SetWall(wallNum, tw, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETWALL):
                insptr++;
                {
                    int const wallNum = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)wallNum >= MAXWALLS, "invalid wall %d", wallNum);

                    tw = *insptr++;
                    Gv_SetVar(*insptr++, VM_GetWall(wallNum, tw));
                    dispatch();
                }

            vInstruction(CON_SETWALLSTRUCT):
                insptr++;
                {
                    int const wallNum = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)wallNum >= MAXWALLS, "invalid wall %d", wallNum);

                    auto const &wallLabel = WallLabels[*insptr++];

                    VM_SetStruct(wallLabel.flags, (intptr_t *)((char *)&wall[wallNum] + wallLabel.offset), Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETWALLSTRUCT):
                insptr++;
                {
                    int const wallNum = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)wallNum >= MAXWALLS, "invalid wall %d", wallNum);
                    auto const &wallLabel = WallLabels[*insptr++];

                    Gv_SetVar(*insptr++, VM_GetStruct(wallLabel.flags, (intptr_t *)((char *)&wall[wallNum] + wallLabel.offset)));
                    dispatch();
                }

            vInstruction(CON_SETACTORVAR):
                insptr++;
                {
                    int const lSprite = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)lSprite >= MAXSPRITES, "invalid sprite %d", lSprite);

                    tw = *insptr++;

                    Gv_SetVar(tw, Gv_GetVar(*insptr++), lSprite, vm.playerNum);

                    dispatch();
                }

            vInstruction(CON_GETACTORVAR):
                insptr++;
                {
                    int const lSprite = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)lSprite >= MAXSPRITES, "invalid sprite %d", lSprite);

                    tw = Gv_GetVar(*insptr++, lSprite, vm.playerNum);

                    Gv_SetVar(*insptr++, tw);

                    dispatch();
                }

            vInstruction(CON_SETPLAYERVAR):
                insptr++;
                {
                    int const playerNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.playerNum;
                    VM_ABORT_IF((unsigned)playerNum >= MAXPLAYERS, "invalid player %d", playerNum);

                    tw = *insptr++;

                    Gv_SetVar(tw, Gv_GetVar(*insptr++), vm.spriteNum, playerNum);

                    dispatch();
                }

            vInstruction(CON_GETPLAYERVAR):
                insptr++;
                {
                    int const playerNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.playerNum;
                    VM_ABORT_IF((unsigned)playerNum >= MAXPLAYERS, "invalid player %d", playerNum);

                    tw = Gv_GetVar(*insptr++, vm.spriteNum, playerNum);

                    Gv_SetVar(*insptr++, tw);

                    dispatch();
                }

            vInstruction(CON_SETACTOR):
                insptr++;
                {
                    int const   spriteNum  = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    auto const &actorLabel = ActorLabels[(tw = *insptr++)];
                    int const   lParm2     = (actorLabel.flags & LABEL_HASPARM2) ? Gv_GetVar(*insptr++) : 0;

                    VM_ABORT_IF(((unsigned)spriteNum >= MAXSPRITES) | ((unsigned)lParm2 > (unsigned)actorLabel.maxParm2),
                              "%s[%d] invalid for sprite %d", actorLabel.name, lParm2, spriteNum);

                    VM_SetSprite(spriteNum, tw, lParm2, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETACTOR):
                insptr++;
                {
                    int const   spriteNum  = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    auto const &actorLabel = ActorLabels[(tw = *insptr++)];
                    int const   lParm2     = (actorLabel.flags & LABEL_HASPARM2) ? Gv_GetVar(*insptr++) : 0;

                    VM_ABORT_IF(((unsigned)spriteNum >= MAXSPRITES) | ((unsigned)lParm2 > (unsigned)actorLabel.maxParm2),
                              "%s[%d] invalid for sprite %d", actorLabel.name, lParm2, spriteNum);

                    Gv_SetVar(*insptr++, VM_GetSprite(spriteNum, tw, lParm2));
                    dispatch();
                }

            vInstruction(CON_SETACTORSTRUCT):
                insptr++;
                {
                    int const spriteNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    auto const &actorLabel = ActorLabels[*insptr++];

                    VM_SetStruct(actorLabel.flags, (intptr_t *)((char *)&actor[spriteNum] + actorLabel.offset), Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETACTORSTRUCT):
                insptr++;
                {
                    int const spriteNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    auto const &actorLabel = ActorLabels[*insptr++];

                    Gv_SetVar(*insptr++, VM_GetStruct(actorLabel.flags, (intptr_t *)((char *)&actor[spriteNum] + actorLabel.offset)));
                    dispatch();
                }

            vInstruction(CON_SETSPRITESTRUCT):
                insptr++;
                {
                    int const spriteNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);
                    auto const &spriteLabel = ActorLabels[*insptr++];

                    VM_SetStruct(spriteLabel.flags, (intptr_t *)((char *)&sprite[spriteNum] + spriteLabel.offset), Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETSPRITESTRUCT):
                insptr++;
                {
                    int const spriteNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    auto const &spriteLabel = ActorLabels[*insptr++];

                    Gv_SetVar(*insptr++, VM_GetStruct(spriteLabel.flags, (intptr_t *)((char *)&sprite[spriteNum] + spriteLabel.offset)));
                    dispatch();
                }
            vInstruction(CON_SETSPRITEEXT):
                insptr++;
                {
                    int const spriteNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    auto const &spriteExtLabel = ActorLabels[*insptr++];

                    VM_SetStruct(spriteExtLabel.flags, (intptr_t *)((char *)&spriteext[spriteNum] + spriteExtLabel.offset), Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETSPRITEEXT):
                insptr++;
                {
                    int const spriteNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    auto const &spriteExtLabel = ActorLabels[*insptr++];

                    Gv_SetVar(*insptr++, VM_GetStruct(spriteExtLabel.flags, (intptr_t *)((char *)&spriteext[spriteNum] + spriteExtLabel.offset)));
                    dispatch();
                }

            vInstruction(CON_SETTSPR):
                insptr++;
                {
                    int const spriteNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    auto const &tsprLabel = TsprLabels[*insptr++];

                    VM_SetStruct(tsprLabel.flags, (intptr_t *)((char *)spriteext[spriteNum].tspr + tsprLabel.offset), Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETTSPR):
                insptr++;
                {
                    int const spriteNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    auto const &tsprLabel = TsprLabels[*insptr++];

                    Gv_SetVar(*insptr++, VM_GetStruct(tsprLabel.flags, (intptr_t *)((char *)spriteext[spriteNum].tspr + tsprLabel.offset)));
                    dispatch();
                }

            vInstruction(CON_SETSECTOR):
                insptr++;
                {
                    int const   sectNum   = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.pSprite->sectnum;
                    VM_ABORT_IF((unsigned)sectNum >= MAXSECTORS, "invalid sector %d", sectNum);

                    tw = *insptr++;

                    VM_SetSector(sectNum, tw, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETSECTOR):
                insptr++;
                {
                    int const   sectNum   = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.pSprite->sectnum;
                    VM_ABORT_IF((unsigned)sectNum >= MAXSECTORS, "invalid sector %d", sectNum);

                    tw = *insptr++;

                    Gv_SetVar(*insptr++, VM_GetSector(sectNum, tw));
                    dispatch();
                }

            vInstruction(CON_SETSECTORSTRUCT):
                insptr++;
                {
                    int const   sectNum   = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.pSprite->sectnum;
                    VM_ABORT_IF((unsigned)sectNum >= MAXSECTORS, "invalid sector %d", sectNum);

                    auto const &sectLabel = SectorLabels[*insptr++];

                    VM_SetStruct(sectLabel.flags, (intptr_t *)((char *)&sector[sectNum] + sectLabel.offset), Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETSECTORSTRUCT):
                insptr++;
                {
                    int const   sectNum   = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.pSprite->sectnum;
                    VM_ABORT_IF((unsigned)sectNum >= MAXSECTORS, "invalid sector %d", sectNum);

                    auto const &sectLabel = SectorLabels[*insptr++];

                    Gv_SetVar(*insptr++, VM_GetStruct(sectLabel.flags, (intptr_t *)((char *)&sector[sectNum] + sectLabel.offset)));
                    dispatch();
                }

            vInstruction(CON_RETURN):
                vm.flags |= VM_RETURN;
#if !defined CON_USE_COMPUTED_GOTO
                fallthrough__;
#endif
            vInstruction(CON_ENDSWITCH):
            vInstruction(CON_ENDA):
            vInstruction(CON_BREAK):
            vInstruction(CON_ENDS):
            vInstruction(CON_ENDEVENT): return;

            vInstruction(CON_JUMP):  // this is used for event chaining
                insptr++;
                tw = Gv_GetVar(*insptr++);
                insptr = (intptr_t *)(tw + apScript);
                dispatch();

            vInstruction(CON_SWITCH):
                insptr++;
                {
                    // command format:
                    // variable ID to check
                    // script offset to 'end'
                    // count of case statements
                    // script offset to default case (null if none)
                    // For each case: value, ptr to code
                    int const lValue    = Gv_GetVar(*insptr++);
                    int const endOffset = *insptr++;
                    int const numCases  = *insptr++;

                    auto lpDefault = insptr++;
                    auto lpCases   = insptr;

                    int left  = 0;
                    int right = numCases - 1;

                    insptr += numCases << 1;

                    do
                    {
                        int const lCheckCase = (left + right) >> 1;

                        if (lpCases[lCheckCase << 1] > lValue)
                            right = lCheckCase - 1;
                        else if (lpCases[lCheckCase << 1] < lValue)
                            left = lCheckCase + 1;
                        else if (lpCases[lCheckCase << 1] == lValue)
                        {
                            // fake a 2-d Array
                            insptr = (intptr_t *)(lpCases[(lCheckCase << 1) + 1] + &apScript[0]);
                            VM_Execute(true);
                            goto matched;
                        }

                        if (right - left < 0)
                            break;
                    } while (1);

                    if (*lpDefault)
                    {
                        insptr = (intptr_t *)(*lpDefault + &apScript[0]);
                        VM_Execute(true);
                    }

                matched:
                    insptr = (intptr_t *)(endOffset + (intptr_t)&apScript[0]);

                    dispatch();
                }

            vInstruction(CON_FOR):  // special-purpose iteration
                insptr++;
                {
                    int const returnVar = *insptr++;
                    int const iterType  = *insptr++;
                    int const nIndex    = iterType <= ITER_DRAWNSPRITES ? 0 : Gv_GetVar(*insptr++);

                    auto const pEnd  = insptr + *insptr;
                    auto const pNext = ++insptr;

                    auto execute = [&](int const index) {
                        Gv_SetVar(returnVar, index);
                        insptr = pNext;
                        VM_Execute();
                        return !!(vm.flags & VM_RETURN);
                    };

                    auto spriteexecute = [&](int const index) {
                        {
                            MICROPROFILE_SCOPE_TOKEN(g_statnumTokens[sprite[index].statnum]);
                            execute(index);
                        }
                        return !!(vm.flags & VM_RETURN);
                    };

                    switch (iterType)
                    {
                        case ITER_ALLSPRITES:
                            for (native_t jj = 0; jj < MAXSPRITES; ++jj)
                            {
                                if (sprite[jj].statnum == MAXSTATUS)
                                    continue;

                                if (spriteexecute(jj))
                                    return;
                            }
                            break;

                        case ITER_ALLSPRITESBYSTAT:
                            for (native_t statNum = 0; statNum < MAXSTATUS; ++statNum)
                            {
                                for (native_t kk, SPRITES_OF_STAT_SAFE(statNum, jj, kk))
                                {
                                    if (spriteexecute(jj))
                                        return;
                                }
                            }
                            break;

                        case ITER_ALLSPRITESBYSECT:
                            for (native_t sectNum = 0; sectNum < numsectors; ++sectNum)
                            {
                                for (native_t kk, SPRITES_OF_SECT_SAFE(sectNum, jj, kk))
                                {
                                    if (spriteexecute(jj))
                                        return;
                                }
                            }
                            break;

                        case ITER_ALLSECTORS:
                            for (native_t jj = 0; jj < numsectors; ++jj)
                                if (execute(jj))
                                    return;
                            break;

                        case ITER_ALLWALLS:
                            for (native_t jj = 0; jj < numwalls; ++jj)
                                if (execute(jj))
                                    return;
                            break;

                        case ITER_ACTIVELIGHTS:
#ifdef POLYMER
                            for (native_t jj = 0; jj < PR_MAXLIGHTS; ++jj)
                            {
                                if (!prlights[jj].flags.active)
                                    continue;

                                if (execute(jj))
                                    return;
                            }
#endif
                            break;

                        case ITER_DRAWNSPRITES:
                            for (native_t jj = 0; jj < spritesortcnt; jj++)
                                if (execute(jj))
                                    return;
                            break;

                        case ITER_SPRITESOFSECTOR:
                            if ((unsigned)nIndex >= MAXSECTORS)
                                goto badindex;

                            for (native_t kk, SPRITES_OF_SECT_SAFE(nIndex, jj, kk))
                            {
                                if (spriteexecute(jj))
                                    return;
                            }
                            break;

                        case ITER_SPRITESOFSTATUS:
                            if ((unsigned)nIndex >= MAXSTATUS)
                                goto badindex;

                            for (native_t kk, SPRITES_OF_STAT_SAFE(nIndex, jj, kk))
                            {
                                if (spriteexecute(jj))
                                    return;
                            }
                            break;

                        case ITER_WALLSOFSECTOR:
                            if ((unsigned)nIndex >= MAXSECTORS)
                                goto badindex;

                            for (native_t jj = sector[nIndex].wallptr, endwall = jj + sector[nIndex].wallnum - 1; jj <= endwall; jj++)
                                if (execute(jj))
                                    return;
                            break;

                        case ITER_LOOPOFWALL:
                            if ((unsigned)nIndex >= (unsigned)numwalls)
                                goto badindex;
                            {
                                int jj = nIndex;
                                do
                                {
                                    if (execute(jj))
                                        return;

                                    jj = wall[jj].point2;
                                } while (jj != nIndex);
                            }
                            break;

                        case ITER_RANGE:
                            for (native_t jj = 0; jj < nIndex; jj++)
                                if (execute(jj))
                                    return;
                            break;
badindex:
                            VLOG_F(LOG_VM, "%s:%d: for %s: index %d out of range!", VM_FILENAME(insptr), VM_DECODE_LINE_NUMBER(g_tw), iter_tokens[iterType].token, nIndex);
                            vm.flags |= VM_RETURN;
                            dispatch();
                    }
                    insptr = pEnd;
                }
                dispatch();

            vInstruction(CON_REDEFINEQUOTE):
                insptr++;
                {
                    int const strIndex  = *insptr++;
                    int const XstrIndex = *insptr++;

                    Bstrcpy(apStrings[strIndex], apXStrings[XstrIndex]);
                    dispatch();
                }

            vInstruction(CON_GETTHISPROJECTILE):
                insptr++;
                {
                    tw = *insptr++;
                    int const spriteNum = (tw != g_thisActorVarID) ? Gv_GetVar(tw) : vm.spriteNum;
                    tw = *insptr++;

                    Gv_SetVar(*insptr++, VM_GetActiveProjectile(spriteNum, tw));
                    dispatch();
                }

            vInstruction(CON_SETTHISPROJECTILE):
                insptr++;
                {
                    tw = *insptr++;
                    int const spriteNum = (tw != g_thisActorVarID) ? Gv_GetVar(tw) : vm.spriteNum;
                    tw = *insptr++;

                    VM_SetActiveProjectile(spriteNum, tw, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_IFCANSHOOTTARGET):
            {
#define CHECK_PICNUM(x)                                                     \
    if ((unsigned)x < MAXSPRITES && sprite[x].picnum == vm.pSprite->picnum) \
    {                                                                       \
        branch(false);                                                      \
        dispatch();                                                         \
    }

                if (vm.playerDist > 1024)
                {
                    int16_t temphit;
                    auto checkHitSprite = [&](int x) {
                        vm.pSprite->ang += x;
                        tw = A_CheckHitSprite(vm.spriteNum, &temphit);
                        vm.pSprite->ang -= x;
                    };

                    if ((tw = A_CheckHitSprite(vm.spriteNum, &temphit)) == (1 << 30))
                    {
                        branch(true);
                        dispatch();
                    }

                    int dist    = 768;
                    int angDiff = 16;

                    if (A_CheckEnemySprite(vm.pSprite) && vm.pSprite->xrepeat > 56)
                    {
                        dist    = 3084;
                        angDiff = 48;
                    }

                    if (tw > dist)
                    {
                        CHECK_PICNUM(temphit);
                        checkHitSprite(angDiff);

                        if (tw > dist)
                        {
                            CHECK_PICNUM(temphit);
                            checkHitSprite(-angDiff);

                            if (tw > 768)
                            {
                                CHECK_PICNUM(temphit);
                                branch(true);
                                dispatch();
                            }
                        }
                    }
                    branch(false);
                    dispatch();
                }
                branch(true);
#undef CHECK_PICNUM
            }
                dispatch();

            vInstruction(CON_IFCANSEETARGET):
                tw = cansee(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - ((krand() & 41) << 8), vm.pSprite->sectnum, vm.pPlayer->pos.x, vm.pPlayer->pos.y,
                            vm.pPlayer->pos.z /*-((krand()&41)<<8)*/, sprite[vm.pPlayer->i].sectnum);
                branch(tw);
                if (tw)
                    vm.pActor->timetosleep = SLEEPTIME;
                dispatch();

            vInstruction(CON_IFACTION):
                branch(AC_ACTION_ID(vm.pData) == *(++insptr));
                dispatch();

            vInstruction(CON_IFACTIONCOUNT):
                branch(AC_ACTION_COUNT(vm.pData) >= *(++insptr));
                dispatch();

            vInstruction(CON_IFACTOR):
                branch(vm.pSprite->picnum == *(++insptr));
                dispatch();

            vInstruction(CON_IFACTORNOTSTAYPUT):
                branch(vm.pActor->stayput == -1);
                dispatch();

            vInstruction(CON_IFAI):
                branch(AC_AI_ID(vm.pData) == *(++insptr));
                dispatch();

            vInstruction(CON_IFBULLETNEAR):
                branch(A_Dodge(vm.pSprite) == 1);
                dispatch();

            vInstruction(CON_IFCEILINGDISTL):
                branch((vm.pSprite->z - vm.pActor->ceilingz) <= (*(++insptr) << 8));
                dispatch();

            vInstruction(CON_IFCLIENT):
                branch(g_netClient != NULL);
                dispatch();

            vInstruction(CON_IFCOUNT):
                branch(AC_COUNT(vm.pData) >= *(++insptr));
                dispatch();

            vInstruction(CON_IFDEAD):
                branch(vm.pSprite->extra <= 0);
                dispatch();

            vInstruction(CON_IFFLOORDISTL):
                branch((vm.pActor->floorz - vm.pSprite->z) <= (*(++insptr) << 8));
                dispatch();

            vInstruction(CON_IFGAPZL):
                branch(((vm.pActor->floorz - vm.pActor->ceilingz) >> 8) < *(++insptr));
                dispatch();

            vInstruction(CON_IFHITSPACE):
                branch(TEST_SYNC_KEY(g_player[vm.playerNum].input.bits, SK_OPEN));
                dispatch();

            vInstruction(CON_IFHITWEAPON):
                branch(A_IncurDamage(vm.spriteNum) >= 0);
                dispatch();

            vInstruction(CON_IFINSPACE):
                branch(G_CheckForSpaceCeiling(vm.pSprite->sectnum));
                dispatch();

            vInstruction(CON_IFINWATER):
                branch(sector[vm.pSprite->sectnum].lotag == ST_2_UNDERWATER);
                dispatch();

            vInstruction(CON_IFONWATER):
                branch(sector[vm.pSprite->sectnum].lotag == ST_1_ABOVE_WATER
                               && klabs(vm.pSprite->z - sector[vm.pSprite->sectnum].floorz) < ZOFFSET5);
                dispatch();

            vInstruction(CON_IFMOVE):
                branch(AC_MOVE_ID(vm.pData) == *(++insptr));
                dispatch();

            vInstruction(CON_IFMULTIPLAYER):
                branch(((g_netServer != nullptr) | (g_netClient != nullptr) | (ud.multimode > 1)));
                dispatch();

            vInstruction(CON_IFOUTSIDE):
                branch(sector[vm.pSprite->sectnum].ceilingstat & 1);
                dispatch();

            vInstruction(CON_IFPLAYBACKON):
                branch(false);
                dispatch();

            vInstruction(CON_IFPLAYERSL):
                branch(numplayers < *(++insptr));
                dispatch();

            vInstruction(CON_IFSERVER):
                branch(g_netServer != NULL);
                dispatch();

            vInstruction(CON_IFSQUISHED):
                branch(VM_CheckSquished());
                dispatch();

            vInstruction(CON_IFSTRENGTH):
                branch(vm.pSprite->extra <= *(++insptr));
                dispatch();

            vInstruction(CON_IFSPAWNEDBY):
            vInstruction(CON_IFWASWEAPON):
                branch(vm.pActor->htpicnum == *(++insptr));
                dispatch();

            vInstruction(CON_IFPDISTL):
                branch(vm.playerDist < *(++insptr));
                if (vm.playerDist > MAXSLEEPDIST && vm.pActor->timetosleep == 0)
                    vm.pActor->timetosleep = SLEEPTIME;
                dispatch();

            vInstruction(CON_IFPDISTG):
                branch(vm.playerDist > *(++insptr));
                if (vm.playerDist > MAXSLEEPDIST && vm.pActor->timetosleep == 0)
                    vm.pActor->timetosleep = SLEEPTIME;
                dispatch();

            vInstruction(CON_IFRESPAWN):
                if (A_CheckEnemySprite(vm.pSprite))
                    branch(ud.respawn_monsters);
                else if (A_CheckInventorySprite(vm.pSprite))
                    branch(ud.respawn_inventory);
                else
                    branch(ud.respawn_items);
                dispatch();

            vInstruction(CON_IFINOUTERSPACE):
                branch(G_CheckForSpaceFloor(vm.pSprite->sectnum));
                dispatch();

            vInstruction(CON_IFNOTMOVING):
                branch((vm.pActor->movflag & 49152) > 16384);
                dispatch();

            vInstruction(CON_IFCANSEE):
            {
                auto pSprite = (uspriteptr_t)&sprite[vm.pPlayer->i];

// select sprite for monster to target
// if holoduke is on, let them target holoduke first.
//
#ifndef EDUKE32_STANDALONE
                if (!FURY && vm.pPlayer->holoduke_on >= 0)
                {
                    pSprite = (uspriteptr_t)&sprite[vm.pPlayer->holoduke_on];
                    tw = cansee(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - (krand() & (ZOFFSET5 - 1)), vm.pSprite->sectnum, pSprite->x, pSprite->y,
                                pSprite->z, pSprite->sectnum);

                    if (tw == 0)
                    {
                        // they can't see player's holoduke
                        // check for player...
                        pSprite = (uspriteptr_t)&sprite[vm.pPlayer->i];
                    }
                }
#endif
                // can they see player, (or player's holoduke)
                tw = cansee(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - (krand() & ((47 << 8))), vm.pSprite->sectnum, pSprite->x, pSprite->y,
                            pSprite->z - (24 << 8), pSprite->sectnum);

                if (tw == 0)
                {
                    // search around for target player
                    // also modifies 'target' x&y if found..

                    tw = (A_FurthestVisiblePoint(vm.spriteNum, pSprite, &vm.pActor->lastv) != -1);
                }
                else
                {
                    // else, they did see it.
                    // save where we were looking...
                    vm.pActor->lastv = pSprite->xy;
                }

                if (tw && ((int)(vm.pSprite->statnum == STAT_ACTOR) | (int)(vm.pSprite->statnum == STAT_STANDABLE)))
                    vm.pActor->timetosleep = SLEEPTIME;

                branch(tw);
                dispatch();
            }

            vInstruction(CON_AI):
                insptr++;
                // Following changed to use pointersizes
                AC_AI_ID(vm.pData)     = *insptr++;                         // Ai
                AC_ACTION_ID(vm.pData) = *(apScript + AC_AI_ID(vm.pData));  // Action

                // NOTE: "if" check added in r1155. It used to be a pointer though.
                if (AC_AI_ID(vm.pData))
                    AC_MOVE_ID(vm.pData) = *(apScript + AC_AI_ID(vm.pData) + 1);  // move

                vm.pSprite->hitag = *(apScript + AC_AI_ID(vm.pData) + 2);  // move flags

                AC_COUNT(vm.pData)        = 0;
                AC_ACTION_COUNT(vm.pData) = 0;
                AC_CURFRAME(vm.pData)     = 0;

                if ((int)!A_CheckEnemySprite(vm.pSprite) | (int)(vm.pSprite->extra > 0))  // hack
                    if (vm.pSprite->hitag & random_angle)
                        vm.pSprite->ang = krand() & 2047;
                dispatch();

            vInstruction(CON_ACTION):
                insptr++;
                AC_ACTION_COUNT(vm.pData) = 0;
                AC_CURFRAME(vm.pData)     = 0;
                AC_ACTION_ID(vm.pData)    = *insptr++;
                dispatch();

            vInstruction(CON_ADDSTRENGTH):
                insptr++;
                vm.pSprite->extra += *insptr++;
                dispatch();

            vInstruction(CON_STRENGTH):
                insptr++;
                vm.pSprite->extra = *insptr++;
                dispatch();

            vInstruction(CON_IFGOTWEAPONCE):
                insptr++;

                if ((g_gametypeFlags[ud.coop] & GAMETYPE_WEAPSTAY) && (g_netServer || ud.multimode > 1))
                {
                    if (*insptr == 0)
                    {
                        int j = 0;
                        for (; j < vm.pPlayer->weapreccnt; ++j)
                            if (vm.pPlayer->weaprecs[j] == vm.pSprite->picnum)
                                break;

                        branch(j < vm.pPlayer->weapreccnt && vm.pSprite->owner == vm.spriteNum);
                        dispatch();
                    }
                    else if (vm.pPlayer->weapreccnt < MAX_WEAPONS)
                    {
                        vm.pPlayer->weaprecs[vm.pPlayer->weapreccnt++] = vm.pSprite->picnum;
                        branch(vm.pSprite->owner == vm.spriteNum);
                        dispatch();
                    }
                }
                branch(false);
                dispatch();

            vInstruction(CON_GETLASTPAL):
                insptr++;
                if (vm.pSprite->picnum == APLAYER)
                    vm.pSprite->pal = g_player[P_GetP(vm.pSprite)].ps->palookup;
                else
                {
                    if (vm.pSprite->pal == 1 && vm.pSprite->extra == 0)  // hack for frozen
                        vm.pSprite->extra++;
                    vm.pSprite->pal = vm.pActor->tempang;
                }
                vm.pActor->tempang = 0;
                dispatch();

            vInstruction(CON_TOSSWEAPON):
                insptr++;
                // NOTE: assumes that current actor is APLAYER
                P_DropWeapon(P_GetP(vm.pSprite));
                dispatch();

            vInstruction(CON_MIKESND):
                insptr++;
                VM_ABORT_IF((unsigned)vm.pSprite->yvel > (unsigned)g_highestSoundIdx, "invalid sound #%d", vm.pUSprite->yvel);
                if (!S_CheckSoundPlaying(vm.pSprite->yvel))
                    A_PlaySound(vm.pSprite->yvel, vm.spriteNum);
                dispatch();

            vInstruction(CON_PKICK):
                insptr++;

                if ((g_netServer || ud.multimode > 1) && vm.pSprite->picnum == APLAYER)
                {
                    if (g_player[otherp].ps->quick_kick == 0)
                        g_player[otherp].ps->quick_kick = 14;
                }
                else if (vm.pSprite->picnum != APLAYER && vm.pPlayer->quick_kick == 0)
                    vm.pPlayer->quick_kick = 14;
                dispatch();

            vInstruction(CON_SIZETO):
                insptr++;

                tw = (*insptr++ - vm.pSprite->xrepeat) << 1;
                vm.pSprite->xrepeat += ksgn(tw);

                if ((vm.pSprite->picnum == APLAYER && vm.pSprite->yrepeat < 36) || *insptr < vm.pSprite->yrepeat
                    || ((vm.pSprite->yrepeat * (tilesiz[vm.pSprite->picnum].y + 8)) << 2) < (vm.pActor->floorz - vm.pActor->ceilingz))
                {
                    tw = ((*insptr) - vm.pSprite->yrepeat) << 1;
                    if (klabs(tw))
                        vm.pSprite->yrepeat += ksgn(tw);
                }

                insptr++;

                dispatch();

            vInstruction(CON_SIZEAT):
                insptr++;
                vm.pSprite->xrepeat = (uint8_t)*insptr++;
                vm.pSprite->yrepeat = (uint8_t)*insptr++;
                dispatch();

            vInstruction(CON_IFACTORSOUND):
                insptr++;
                {
                    int const spriteNum = Gv_GetVar(*insptr++);
                    int const soundNum  = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)soundNum > (unsigned)g_highestSoundIdx, "invalid sound #%d", soundNum);

                    insptr--;
                    branch(A_CheckSoundPlaying(spriteNum, soundNum));
                }
                dispatch();

            vInstruction(CON_IFSOUND):
                insptr++;
                VM_ABORT_IF((unsigned)*insptr > (unsigned)g_highestSoundIdx, "invalid sound #%d", (int32_t)*insptr);
                branch(S_CheckSoundPlaying(*insptr));
                //    VM_DoConditional(SoundOwner[*insptr][0].ow == vm.spriteNum);
                dispatch();

            vInstruction(CON_STOPACTORSOUND):
                insptr++;
                {
                    int const spriteNum = Gv_GetVar(*insptr++);
                    int const soundNum  = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)soundNum > (unsigned)g_highestSoundIdx, "invalid sound #%d", soundNum);

                    if (A_CheckSoundPlaying(spriteNum, soundNum))
                        S_StopEnvSound(soundNum, spriteNum);

                    dispatch();
                }

            vInstruction(CON_ACTORSOUND):
                insptr++;
                {
                    int const spriteNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.spriteNum;
                    int const soundNum  = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)soundNum > (unsigned)g_highestSoundIdx, "invalid sound #%d", soundNum);

                    A_PlaySound(soundNum, spriteNum);

                    dispatch();
                }

            vInstruction(CON_SETACTORSOUNDPITCH):
                insptr++;
                {
                    int const spriteNum = Gv_GetVar(*insptr++);
                    int const soundNum  = Gv_GetVar(*insptr++);
                    int const newPitch  = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)soundNum > (unsigned)g_highestSoundIdx, "invalid sound #%d", soundNum);

                    S_ChangeSoundPitch(soundNum, spriteNum, newPitch);

                    dispatch();
                }

            vInstruction(CON_TIP):
                insptr++;
                vm.pPlayer->tipincs = GAMETICSPERSEC;
                dispatch();

            vInstruction(CON_FALL):
                insptr++;
                VM_Fall(vm.spriteNum, vm.pSprite);
                dispatch();

            vInstruction(CON_NULLOP): insptr++; dispatch();

            vInstruction(CON_ADDAMMO):
                insptr++;
                {
                    int const weaponNum = *insptr++;
                    int const addAmount = *insptr++;

                    VM_AddAmmo(vm.pPlayer, weaponNum, addAmount);

                    dispatch();
                }

            vInstruction(CON_MONEY):
                insptr++;
                A_SpawnMultiple(vm.spriteNum, MONEY, *insptr++);
                dispatch();

            vInstruction(CON_MAIL):
                insptr++;
                A_SpawnMultiple(vm.spriteNum, MAIL, *insptr++);
                dispatch();

            vInstruction(CON_SLEEPTIME):
                insptr++;
                vm.pActor->timetosleep = (int16_t)*insptr++;
                dispatch();

            vInstruction(CON_PAPER):
                insptr++;
                A_SpawnMultiple(vm.spriteNum, PAPER, *insptr++);
                dispatch();

            vInstruction(CON_ADDKILLS):
                insptr++;
                P_AddKills(vm.pPlayer, *insptr++);
                vm.pActor->stayput = -1;
                dispatch();

            vInstruction(CON_LOTSOFGLASS):
                insptr++;
#ifndef EDUKE32_STANDALONE
                if (!FURY)
                    A_SpawnGlass(vm.spriteNum, *insptr++);
#else
                insptr++;
#endif
                dispatch();

            vInstruction(CON_SPAWNWALLGLASS):
                insptr++;
                {
#ifndef EDUKE32_STANDALONE
                    if (!FURY)
                    {
                        int const wallNum   = Gv_GetVar(*insptr++);
                        int const numShards = Gv_GetVar(*insptr++);
                        A_SpawnWallGlass(vm.spriteNum, wallNum, numShards);
                    }
#else
                    Gv_GetVar(*insptr++);
                    Gv_GetVar(*insptr++);
#endif
                }
                dispatch();

            vInstruction(CON_SPAWNWALLSTAINEDGLASS):
                insptr++;
                {
#ifndef EDUKE32_STANDALONE
                    if (!FURY)
                    {
                        int const wallNum   = Gv_GetVar(*insptr++);
                        int const numShards = Gv_GetVar(*insptr++);
                        A_SpawnRandomGlass(vm.spriteNum, wallNum, numShards);
                    }
#else
                    Gv_GetVar(*insptr++);
                    Gv_GetVar(*insptr++);
#endif
                }
                dispatch();

            vInstruction(CON_SPAWNCEILINGGLASS):
                insptr++;
                {
#ifndef EDUKE32_STANDALONE
                    if (!FURY)
                    {
                        int const sectNum   = Gv_GetVar(*insptr++);
                        int const numShards = Gv_GetVar(*insptr++);
                        A_SpawnCeilingGlass(vm.spriteNum, sectNum, numShards);
                    }
#else
                    Gv_GetVar(*insptr++);
                    Gv_GetVar(*insptr++);
#endif
                }
                dispatch();

            vInstruction(CON_KILLIT):
                insptr++;
                vm.flags |= VM_KILL;
                return;

            vInstruction(CON_DEBUG):
                insptr++;
                VLOG_F(LOG_VM, "%d", int(*insptr++));
#ifndef NDEBUG
                debug_break();
#endif
                dispatch();

            vInstruction(CON_ENDOFGAME):
            vInstruction(CON_ENDOFLEVEL):
                insptr++;
                vm.pPlayer->timebeforeexit  = *insptr++;
                vm.pPlayer->customexitsound = -1;
                ud.eog = 1;
                dispatch();

            vInstruction(CON_ADDPHEALTH):
                insptr++;

                {
                    if (vm.pPlayer->newowner >= 0)
                        G_ClearCameraView(vm.pPlayer);

                    int newHealth = sprite[vm.pPlayer->i].extra;

#ifndef EDUKE32_STANDALONE
                    if (!FURY && vm.pSprite->picnum == ATOMICHEALTH)
                    {
                        if (newHealth > 0)
                            newHealth += *insptr;
                        if (newHealth > (vm.pPlayer->max_player_health << 1))
                            newHealth = (vm.pPlayer->max_player_health << 1);
                    }
                    else
#endif
                    {
                        if (newHealth > vm.pPlayer->max_player_health && *insptr > 0)
                        {
                            insptr++;
                            dispatch();
                        }
                        else
                        {
                            if (newHealth > 0)
                                newHealth += *insptr;
                            if (newHealth > vm.pPlayer->max_player_health && *insptr > 0)
                                newHealth = vm.pPlayer->max_player_health;
                        }
                    }

                    if (newHealth < 0)
                        newHealth = 0;

                    if (ud.god == 0)
                    {
                        if (*insptr > 0)
                        {
#ifndef EDUKE32_STANDALONE
                            if (!FURY && (newHealth - *insptr) < (vm.pPlayer->max_player_health >> 2) && newHealth >= (vm.pPlayer->max_player_health >> 2))
                                A_PlaySound(DUKE_GOTHEALTHATLOW, vm.pPlayer->i);
#endif
                            vm.pPlayer->last_extra = newHealth;
                        }

                        sprite[vm.pPlayer->i].extra = newHealth;
                    }
                }

                insptr++;
                dispatch();

            vInstruction(CON_MOVE):
                insptr++;
                AC_COUNT(vm.pData)   = 0;
                AC_MOVE_ID(vm.pData) = *insptr++;
                vm.pSprite->hitag    = *insptr++;

                if ((int)!A_CheckEnemySprite(vm.pSprite) | (int)(vm.pSprite->extra > 0))  // hack
                    if (vm.pSprite->hitag & random_angle)
                        vm.pSprite->ang = krand() & 2047;
                dispatch();

            vInstruction(CON_ADDWEAPON):
                insptr++;
                {
                    int const weaponNum = Gv_GetVar(*insptr++);
                    VM_AddWeapon(vm.pPlayer, weaponNum, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_SETASPECT):
                insptr++;
                {
                    int const xRange = Gv_GetVar(*insptr++);
                    renderSetAspect(xRange, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_SSP):
                insptr++;
                {
                    int const spriteNum = Gv_GetVar(*insptr++);
                    int const clipType  = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    A_SetSprite(spriteNum, clipType);
                    dispatch();
                }

            vInstruction(CON_ACTIVATEBYSECTOR):
                insptr++;
                {
                    int const sectNum   = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)sectNum >= MAXSECTORS, "invalid sector %d", sectNum);

                    int const spriteNum = Gv_GetVar(*insptr++);

                    G_ActivateBySector(sectNum, spriteNum);
                    dispatch();
                }

            vInstruction(CON_OPERATESECTORS):
                insptr++;
                {
                    int const sectNum   = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)sectNum >= MAXSECTORS, "invalid sector %d", sectNum);

                    int const spriteNum = Gv_GetVar(*insptr++);

                    G_OperateSectors(sectNum, spriteNum);
                    dispatch();
                }

            vInstruction(CON_OPERATEACTIVATORS):
                insptr++;
                {
                    int const nTag      = Gv_GetVar(*insptr++);
                    int const playerNum = (*insptr++ == g_thisActorVarID) ? vm.playerNum : Gv_GetVar(insptr[-1]);

                    VM_ABORT_IF((unsigned)playerNum >= MAXPLAYERS, "invalid player %d", playerNum);

                    G_OperateActivators(nTag, playerNum);
                    dispatch();
                }

            vInstruction(CON_CANSEESPR):
                insptr++;
                {
                    int const nSprite1 = Gv_GetVar(*insptr++);
                    int const nSprite2 = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(((unsigned)nSprite1 >= MAXSPRITES) | ((unsigned)nSprite2 >= MAXSPRITES), "invalid sprite pair (%d, %d)", nSprite1, nSprite2);

                    int const nResult = cansee(sprite[nSprite1].x, sprite[nSprite1].y, sprite[nSprite1].z, sprite[nSprite1].sectnum,
                                               sprite[nSprite2].x, sprite[nSprite2].y, sprite[nSprite2].z, sprite[nSprite2].sectnum);

                    Gv_SetVar(*insptr++, nResult);
                    dispatch();
                }

            vInstruction(CON_OPERATERESPAWNS):
                insptr++;
                G_OperateRespawns(Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_OPERATEMASTERSWITCHES):
                insptr++;
                G_OperateMasterSwitches(Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_CHECKACTIVATORMOTION):
                insptr++;
                aGameVars[g_returnVarID].global = G_CheckActivatorMotion(Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_INSERTSPRITEQ):
                insptr++;
                A_AddToDeleteQueue(vm.spriteNum);
                dispatch();

            vInstruction(CON_QSTRLEN):
                insptr++;
                {
                    int const gameVar  = *insptr++;
                    int const quoteNum = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(bad_quote(quoteNum), "invalid quote %d", quoteNum);

                    Gv_SetVar(gameVar, Bstrlen(apStrings[quoteNum]));
                    dispatch();
                }

            vInstruction(CON_QSTRDIM):
                insptr++;
                {
                    int const widthVar  = *insptr++;
                    int const heightVar = *insptr++;

                    struct
                    {
                        int32_t tileNum;
                        vec3_t  vect;
                        int32_t blockAngle, quoteNum, orientation;
                        vec2_t  offset, between;
                        int32_t f;
                        vec2_t  bound[2];
                    } v;
                    Gv_FillWithVars(v);

                    if (EDUKE32_PREDICT_FALSE((v.tileNum < 0) | (v.tileNum + 127 >= MAXTILES)))
                        CON_ERRPRINTF("invalid base tilenum %d", v.tileNum);
                    else if (EDUKE32_PREDICT_FALSE(bad_quote(v.quoteNum)))
                        CON_ERRPRINTF("invalid quote %d", v.quoteNum);
                    else
                    {
                        if (!(v.orientation & ROTATESPRITE_FULL16))
                        {
                            v.vect.x <<= 16;
                            v.vect.y <<= 16;
                            v.offset.x <<= 16;
                            v.offset.y <<= 16;
                            v.between.x <<= 16;
                            v.between.y <<= 16;
                        }

                        G_SetScreenTextEmpty(v.offset, v.tileNum, v.f);

                        vec2_t dim = G_ScreenTextSize(v.tileNum, v.vect.x, v.vect.y, v.vect.z, v.blockAngle, apStrings[v.quoteNum], 2 | v.orientation,
                                                      v.offset.x, v.offset.y, v.between.x, v.between.y, v.f, v.bound[0].x, v.bound[0].y, v.bound[1].x,
                                                      v.bound[1].y);

                        if (!(v.orientation & ROTATESPRITE_FULL16))
                        {
                            dim.x >>= 16;
                            dim.y >>= 16;
                        }

                        Gv_SetVar(widthVar, dim.x);
                        Gv_SetVar(heightVar, dim.y);
                    }
                    dispatch();
                }

            vInstruction(CON_HEADSPRITESTAT):
                insptr++;
                {
                    int const gameVar = *insptr++;
                    int const statNum = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)statNum >= MAXSTATUS, "invalid status list %d", statNum);

                    Gv_SetVar(gameVar, headspritestat[statNum]);
                    dispatch();
                }

            vInstruction(CON_PREVSPRITESTAT):
                insptr++;
                {
                    int const gameVar   = *insptr++;
                    int const spriteNum = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    Gv_SetVar(gameVar, prevspritestat[spriteNum]);
                    dispatch();
                }

            vInstruction(CON_NEXTSPRITESTAT):
                insptr++;
                {
                    int const gameVar   = *insptr++;
                    int const spriteNum = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    Gv_SetVar(gameVar, nextspritestat[spriteNum]);
                    dispatch();
                }

            vInstruction(CON_HEADSPRITESECT):
                insptr++;
                {
                    int const gameVar = *insptr++;
                    int const sectNum = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)sectNum >= MAXSECTORS, "invalid sector %d", sectNum);

                    Gv_SetVar(gameVar, headspritesect[sectNum]);
                    dispatch();
                }

            vInstruction(CON_PREVSPRITESECT):
                insptr++;
                {
                    int const gameVar   = *insptr++;
                    int const spriteNum = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    Gv_SetVar(gameVar, prevspritesect[spriteNum]);
                    dispatch();
                }

            vInstruction(CON_NEXTSPRITESECT):
                insptr++;
                {
                    int const gameVar   = *insptr++;
                    int const spriteNum = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)spriteNum >= MAXSPRITES, "invalid sprite %d", spriteNum);

                    Gv_SetVar(gameVar, nextspritesect[spriteNum]);
                    dispatch();
                }

            vInstruction(CON_GETKEYNAME):
                insptr++;
                {
                    int const quoteIndex = Gv_GetVar(*insptr++);
                    int const gameFunc   = Gv_GetVar(*insptr++);
                    int const funcPos    = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(bad_quote(quoteIndex), "invalid quote %d", quoteIndex);
                    VM_ABORT_IF((unsigned)gameFunc >= NUMGAMEFUNCTIONS, "invalid function %d", gameFunc);

                    if (funcPos < 2)
                        Bstrcpy(tempbuf, KB_ScanCodeToString(ud.config.KeyboardKeys[gameFunc][funcPos]));
                    else
                    {
                        Bstrcpy(tempbuf, KB_ScanCodeToString(ud.config.KeyboardKeys[gameFunc][0]));

                        if (!*tempbuf)
                            Bstrcpy(tempbuf, KB_ScanCodeToString(ud.config.KeyboardKeys[gameFunc][1]));
                    }

                    if (*tempbuf)
                        Bstrcpy(apStrings[quoteIndex], tempbuf);

                    dispatch();
                }

            vInstruction(CON_GETGAMEFUNCBIND):
                insptr++;
                {
                    int const quoteIndex = Gv_GetVar(*insptr++);
                    VM_ABORT_IF(((unsigned)quoteIndex >= MAXQUOTES) | (apStrings[quoteIndex & (MAXQUOTES-1)] == nullptr), "invalid quote %d", quoteIndex);

                    int const gameFunc   = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)gameFunc >= NUMGAMEFUNCTIONS, "invalid function %d", gameFunc);

                    static char const s_KeyboardFormat[] = "[%s]";
                    static char const s_JoystickFormat[] = "(%s)";
                    static char const s_Unbound[] = "UNBOUND";

                    auto getkeyname = [&](void)
                    {
                        char const *keyname = CONFIG_GetGameFuncOnKeyboard(gameFunc);
                        if (keyname != nullptr && keyname[0] != '\0')
                        {
                            snprintf(apStrings[quoteIndex], MAXQUOTELEN, s_KeyboardFormat, keyname);
                            return true;
                        }
                        return false;
                    };

                    auto getjoyname = [&](void)
                    {
                        char const *joyname = CONFIG_GetGameFuncOnJoystick(gameFunc);
                        if (joyname != nullptr && joyname[0] != '\0')
                        {
                            snprintf(apStrings[quoteIndex], MAXQUOTELEN, s_JoystickFormat, joyname);
                            return true;
                        }
                        return false;
                    };

                    if (CONTROL_LastSeenInput == LastSeenInput::Joystick)
                    {
                        if (getjoyname() || getkeyname()) dispatch();
                        snprintf(apStrings[quoteIndex], MAXQUOTELEN, s_JoystickFormat, s_Unbound);
                    }
                    else
                    {
                        if (getkeyname() || getjoyname()) dispatch();
                        snprintf(apStrings[quoteIndex], MAXQUOTELEN, s_KeyboardFormat, s_Unbound);
                    }

                    dispatch();
                }

            vInstruction(CON_QSUBSTR):
                insptr++;
                {
                    struct
                    {
                        int32_t outputQuote, inputQuote, quotePos, quoteLength;
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF(bad_quote(v.outputQuote) | bad_quote(v.inputQuote),
                              "invalid quote %d", (unsigned)v.outputQuote < MAXQUOTES && apStrings[v.outputQuote] ? v.inputQuote : v.outputQuote);
                    VM_ABORT_IF((unsigned)v.quotePos >= MAXQUOTELEN, "invalid position %d", v.quotePos);
                    VM_ABORT_IF(v.quoteLength <= 0, "invalid length %d", v.quoteLength);

                    char *      pOutput = apStrings[v.outputQuote];
                    char const *pInput  = apStrings[v.inputQuote];

                    while (*pInput && v.quotePos--)
                        pInput++;
                    while ((*pOutput = *pInput) && v.quoteLength--)
                    {
                        pOutput++;
                        pInput++;
                    }
                    *pOutput = '\0';

                    dispatch();
                }

            vInstruction(CON_QSTRCMP):
                insptr++;
                {
                    int const quote1  = Gv_GetVar(*insptr++);
                    int const quote2  = Gv_GetVar(*insptr++);
                    int const gameVar = *insptr++;

                    VM_ABORT_IF(bad_quote(quote1) | bad_quote(quote2),
                              "invalid quote %d", (unsigned)quote1 < MAXQUOTES && apStrings[quote1] != NULL ? quote2 : quote1);

                    Gv_SetVar(gameVar, strcmp(apStrings[quote1], apStrings[quote2]));
                    dispatch();
                }

            vInstruction(CON_GETPNAME):
            vInstruction(CON_QSTRNCAT):
            vInstruction(CON_QSTRCAT):
            vInstruction(CON_QSTRCPY):
            vInstruction(CON_QGETSYSSTR):
                insptr++;
                {
                    int const q = Gv_GetVar(*insptr++);
                    int j;
                    if (VM_DECODE_INST(tw) == CON_GETPNAME && *insptr == g_thisActorVarID)
                    {
                        j = vm.playerNum;
                        insptr++;
                    }
                    else
                        j = Gv_GetVar(*insptr++);

                    switch (VM_DECODE_INST(tw))
                    {
                        case CON_GETPNAME:
                            VM_ABORT_IF(bad_quote(q), "invalid quote %d", q);
                            if (g_player[j].user_name[0])
                                Bstrcpy(apStrings[q], g_player[j].user_name);
                            else
                                Bsprintf(apStrings[q], "%d", j);
                            break;
                        case CON_QGETSYSSTR:
                            VM_ABORT_IF(bad_quote(q), "invalid quote %d", q);
                            switch (j)
                            {
                                case STR_MAPNAME:
                                case STR_MAPFILENAME:
                                {
                                    if (G_HaveUserMap())
                                    {
                                        snprintf(apStrings[q], MAXQUOTELEN, "%s", boardfilename);
                                        break;
                                    }

                                    int const levelNum = ud.volume_number * MAXLEVELS + ud.level_number;
                                    const char *pName;

                                    VM_ABORT_IF((unsigned)levelNum >= ARRAY_SIZE(g_mapInfo), "out of bounds map number (vol=%d, lev=%d)",
                                              ud.volume_number, ud.level_number);

                                    pName = j == STR_MAPNAME ? g_mapInfo[levelNum].name : g_mapInfo[levelNum].filename;

                                    VM_ABORT_IF(pName == nullptr, "attempted access to %s of non-existent map (vol=%d, lev=%d)",
                                                      j == STR_MAPNAME ? "name" : "file name", ud.volume_number, ud.level_number);

                                    Bstrcpy(apStrings[q], j == STR_MAPNAME ? g_mapInfo[levelNum].name : g_mapInfo[levelNum].filename);
                                    break;
                                }
                                case STR_PLAYERNAME:
                                    VM_ABORT_IF((unsigned)vm.playerNum >= MAXPLAYERS, "invalid player %d", vm.playerNum);
                                    Bstrcpy(apStrings[q], g_player[vm.playerNum].user_name);
                                    break;
                                case STR_VERSION:
                                    Bsprintf(tempbuf, HEAD2 " %s", s_buildRev);
                                    Bstrcpy(apStrings[q], tempbuf);
                                    break;
                                case STR_GAMETYPE: Bstrcpy(apStrings[q], g_gametypeNames[ud.coop]); break;
                                case STR_VOLUMENAME:
                                    if (G_HaveUserMap())
                                    {
                                        apStrings[q][0] = '\0';
                                        break;
                                    }

                                    VM_ABORT_IF((unsigned)ud.volume_number >= MAXVOLUMES, "invalid volume %d", ud.volume_number);
                                    Bstrcpy(apStrings[q], g_volumeNames[ud.volume_number]);
                                    break;
                                case STR_REVISION:        Bstrcpy(apStrings[q], &s_buildRev[1]);        break;
                                case STR_YOURTIME:        Bstrcpy(apStrings[q], G_PrintYourTime());     break;
                                case STR_PARTIME:         Bstrcpy(apStrings[q], G_PrintParTime());      break;
                                case STR_DESIGNERTIME:    Bstrcpy(apStrings[q], G_PrintDesignerTime()); break;
                                case STR_BESTTIME:        Bstrcpy(apStrings[q], G_PrintBestTime());     break;
                                case STR_USERMAPFILENAME: snprintf(apStrings[q], MAXQUOTELEN, "%s", boardfilename); break;
                                default: CON_ERRPRINTF("invalid string index %d or %d", q, j); abort_after_error();
                            }
                            break;
                        case CON_QSTRCAT:
                            if (EDUKE32_PREDICT_FALSE((apStrings[q] == NULL) | (apStrings[j] == NULL)))
                                goto nullquote;
                            Bstrncat(apStrings[q], apStrings[j], (MAXQUOTELEN - 1) - Bstrlen(apStrings[q]));
                            break;
                        case CON_QSTRNCAT:
                            if (EDUKE32_PREDICT_FALSE((apStrings[q] == NULL) | (apStrings[j] == NULL)))
                                goto nullquote;
                            Bstrncat(apStrings[q], apStrings[j], Gv_GetVar(*insptr++));
                            break;
                        case CON_QSTRCPY:
                            if (EDUKE32_PREDICT_FALSE((apStrings[q] == NULL) | (apStrings[j] == NULL)))
                                goto nullquote;
                            if (q != j)
                                Bstrcpy(apStrings[q], apStrings[j]);
                            break;
                        default:
                        nullquote:
                            CON_ERRPRINTF("invalid quote %d", apStrings[q] ? j : q);
                            abort_after_error();
                    }
                    dispatch();
                }

            vInstruction(CON_CHANGESPRITESECT):
                insptr++;
                {
                    int const spriteNum = Gv_GetVar(*insptr++);
                    int const sectNum   = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(((unsigned)spriteNum >= MAXSPRITES) | ((unsigned)sectNum >= MAXSECTORS), "invalid parameters: %d, %d", spriteNum, sectNum);

                    if (sprite[spriteNum].sectnum == sectNum)
                        dispatch();

                    changespritesect(spriteNum, sectNum);
                    dispatch();
                }

            vInstruction(CON_CHANGESPRITESTAT):
                insptr++;
                {
                    int const spriteNum = Gv_GetVar(*insptr++);
                    int const statNum   = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(((unsigned)spriteNum >= MAXSPRITES) | ((unsigned)statNum >= MAXSTATUS), "invalid parameters: %d, %d", spriteNum, statNum);

                    if (sprite[spriteNum].statnum == statNum)
                        dispatch();

                    /* initialize actor data when changing to an actor statnum because there's usually
                    garbage left over from being handled as a hard coded object */

                    if (sprite[spriteNum].statnum > STAT_ZOMBIEACTOR && ((statNum == STAT_ACTOR) | (statNum == STAT_ZOMBIEACTOR)))
                    {
                        auto pActor = &actor[spriteNum];
                        auto pSprite = &sprite[spriteNum];

                        Bmemset(&pActor->t_data, 0, sizeof pActor->t_data);

                        pActor->lastv       = { 0, 0 };
                        pActor->timetosleep = 0;
                        pActor->cgg         = 0;
                        pActor->movflag     = 0;
                        pActor->tempang     = 0;
                        pActor->dispicnum   = 0;
                        pActor->flags       = 0;
                        pSprite->hitag      = 0;

                        if (G_TileHasActor(pSprite->picnum))
                        {
                            auto actorptr = g_tile[pSprite->picnum].execPtr;
                            // offsets
                            AC_ACTION_ID(pActor->t_data) = actorptr[1];
                            AC_MOVE_ID(pActor->t_data)   = actorptr[2];
                            AC_MOVFLAGS(pSprite, pActor) = actorptr[3];  // ai bits (movflags)
                        }
                    }

                    changespritestat(spriteNum, statNum);
                    dispatch();
                }

            vInstruction(CON_STARTLEVEL):
                insptr++;  // skip command
                {
                    // from 'level' cheat in game.c (about line 6250)
                    int const volumeNum = Gv_GetVar(*insptr++);
                    int const levelNum  = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(((unsigned)volumeNum >= MAXVOLUMES) | ((unsigned)levelNum >= MAXLEVELS), "invalid parameters: %d, %d", volumeNum, levelNum);

                    ud.m_volume_number = ud.volume_number = volumeNum;
                    ud.m_level_number = ud.level_number = levelNum;
                    // if (numplayers > 1 && g_netServer)
                    //    Net_NewGame(volnume,levnume);
                     //else
                    {
                        g_player[myconnectindex].ps->gm |= MODE_EOL;
                        ud.display_bonus_screen = 0;
                    }  // MODE_RESTART;

                    dispatch();
                }

            vInstruction(CON_MYOSX):
            vInstruction(CON_MYOSPALX):
            vInstruction(CON_MYOS):
            vInstruction(CON_MYOSPAL):
                insptr++;
                {
                    struct
                    {
                        vec2_t  pos;
                        int32_t tilenum, shade, orientation;
                    } v;
                    Gv_FillWithVars(v);

                    switch (VM_DECODE_INST(tw))
                    {
                        case CON_MYOS: VM_DrawTile(v.pos.x, v.pos.y, v.tilenum, v.shade, v.orientation); break;
                        case CON_MYOSPAL: VM_DrawTilePal(v.pos.x, v.pos.y, v.tilenum, v.shade, v.orientation, Gv_GetVar(*insptr++)); break;
                        case CON_MYOSX: VM_DrawTileSmall(v.pos.x, v.pos.y, v.tilenum, v.shade, v.orientation); break;
                        case CON_MYOSPALX: VM_DrawTilePalSmall(v.pos.x, v.pos.y, v.tilenum, v.shade, v.orientation, Gv_GetVar(*insptr++)); break;
                    }
                    dispatch();
                }

            vInstruction(CON_DISPLAYRAND):
                insptr++;
                Gv_SetVar(*insptr++, system_15bit_rand());
                dispatch();

            vInstruction(CON_DRAGPOINT):
                insptr++;
                {
                    int const wallNum = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)wallNum >= (unsigned)numwalls, "invalid wall %d", wallNum);

                    vec2_t    n;
                    Gv_FillWithVars(n);

                    dragpoint(wallNum, n.x, n.y, 0);
                    dispatch();
                }

            vInstruction(CON_LDIST):
            vInstruction(CON_DIST):
                insptr++;
                {
                    int const out = *insptr++;
                    vec2_t    in;
                    Gv_FillWithVars(in);

                    VM_ABORT_IF(((unsigned)in.x >= MAXSPRITES) | ((unsigned)in.y >= MAXSPRITES), "invalid sprite pair (%d, %d)", in.x, in.y);

                    Gv_SetVar(out, (VM_DECODE_INST(tw) == CON_LDIST ? ldist : dist)(&sprite[in.x], &sprite[in.y]));
                    dispatch();
                }

            vInstruction(CON_GETANGLE):
            vInstruction(CON_GETINCANGLE):
                insptr++;
                {
                    int const out = *insptr++;
                    vec2_t    in;
                    Gv_FillWithVars(in);
                    Gv_SetVar(out, (VM_DECODE_INST(tw) == CON_GETANGLE ? getangle : G_GetAngleDelta)(in.x, in.y));
                    dispatch();
                }

            vInstruction(CON_MULSCALE):
            vInstruction(CON_DIVSCALE):
                insptr++;
                {
                    int const out = *insptr++;
                    vec3_t    in;
                    Gv_FillWithVars(in);

                    if (VM_DECODE_INST(tw) == CON_MULSCALE)
                        Gv_SetVar(out, mulscale(in.x, in.y, in.z));
                    else
                        Gv_SetVar(out, divscale(in.x, in.y, in.z));

                    dispatch();
                }

            vInstruction(CON_SCALEVAR):
                insptr++;
                {
                    int const out = *insptr++;
                    vec3_t    in;
                    Gv_FillWithVars(in);
                    Gv_SetVar(out, scale(in.x, in.y, in.z));
                    dispatch();
                }

            vInstruction(CON_INITTIMER):
                insptr++;
                G_InitTimer(Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_NEXTSECTORNEIGHBORZ):
                insptr++;
                {
                    int32_t params[4];
                    Gv_FillWithVars(params);
                    aGameVars[g_returnVarID].global = nextsectorneighborz(params[0], params[1], params[2], params[3]);
                }
                dispatch();

            vInstruction(CON_MOVESECTOR):
                insptr++;
                A_MoveSector(Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_TIME): insptr += 2; dispatch();

            vInstruction(CON_ESPAWN):
            vInstruction(CON_EQSPAWN):
            vInstruction(CON_QSPAWN):
                insptr++;
                {
                    VM_ABORT_IF((unsigned)vm.pSprite->sectnum >= MAXSECTORS, "invalid sector %d", vm.pUSprite->sectnum);

                    int const tileNum = Gv_GetVar(*insptr++);
                    int const spriteNum = A_Spawn(vm.spriteNum, tileNum);

                    switch (VM_DECODE_INST(tw))
                    {
                        case CON_EQSPAWN:
                            if (spriteNum != -1)
                                A_AddToDeleteQueue(spriteNum);
                            fallthrough__;
                        case CON_ESPAWN: aGameVars[g_returnVarID].global = spriteNum; break;
                        case CON_QSPAWN:
                            if (spriteNum != -1)
                                A_AddToDeleteQueue(spriteNum);
                            break;
                    }
                    dispatch();
                }

            vInstruction(CON_SHOOT):
            vInstruction(CON_ESHOOT):
                insptr++;
                {
                    VM_ABORT_IF((unsigned)vm.pSprite->sectnum >= MAXSECTORS, "invalid sector %d", vm.pUSprite->sectnum);

                    int j = Gv_GetVar(*insptr++);
                    j = A_Shoot(vm.spriteNum, j);

                    if (VM_DECODE_INST(tw) == CON_ESHOOT)
                        aGameVars[g_returnVarID].global = j;

                    dispatch();
                }

            vInstruction(CON_EZSHOOT):
            vInstruction(CON_ZSHOOT):
                insptr++;
                {
                    VM_ABORT_IF((unsigned)vm.pSprite->sectnum >= MAXSECTORS, "invalid sector %d", vm.pUSprite->sectnum);

                    int const zvel = (int16_t)Gv_GetVar(*insptr++);
                    int j = A_ShootWithZvel(vm.spriteNum, Gv_GetVar(*insptr++), zvel);

                    if (VM_DECODE_INST(tw) == CON_EZSHOOT)
                        aGameVars[g_returnVarID].global = j;

                    dispatch();
                }

            vInstruction(CON_CMENU):
                insptr++;
                Menu_Change(Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_SOUND):
            vInstruction(CON_STOPSOUND):
            vInstruction(CON_SOUNDONCE):
            vInstruction(CON_GLOBALSOUND):
            vInstruction(CON_SCREENSOUND):
                insptr++;
                {
                    int const soundNum = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)soundNum > (unsigned)g_highestSoundIdx, "invalid sound #%d", soundNum);

                    switch (VM_DECODE_INST(tw))
                    {
                        case CON_SOUNDONCE:
                            if (!S_CheckSoundPlaying(soundNum))
                            {
                                fallthrough__;
                                case CON_SOUND: A_PlaySound((int16_t)soundNum, vm.spriteNum);
                            }
                            dispatch();
                        case CON_GLOBALSOUND: A_PlaySound((int16_t)soundNum, g_player[screenpeek].ps->i); dispatch();
                        case CON_STOPSOUND:
                            if (S_CheckSoundPlaying(soundNum))
                                S_StopSound((int16_t)soundNum);
                            dispatch();
                        case CON_SCREENSOUND: S_PlaySound(soundNum); dispatch();
                    }
                }
                dispatch();

            vInstruction(CON_STARTCUTSCENE):
            vInstruction(CON_IFCUTSCENE):
                insptr++;
                {
                    int const nQuote = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(bad_quote(nQuote), "invalid quote %d", nQuote);

                    if (VM_DECODE_INST(tw) == CON_IFCUTSCENE)
                    {
                        insptr--;
                        branch(g_animPtr == Anim_Find(apStrings[nQuote]));
                        dispatch();
                    }

                    tw = vm.pPlayer->palette;
                    Anim_Play(apStrings[nQuote]);
                    P_SetGamePalette(vm.pPlayer, tw, 2 + 16);
                    dispatch();
                }

            vInstruction(CON_STARTSCREEN):
                insptr++;
                Screen_Play();
                dispatch();

            vInstruction(CON_GUNIQHUDID):
                insptr++;
                {
                    tw = Gv_GetVar(*insptr++);
                    if (EDUKE32_PREDICT_FALSE((unsigned)tw >= MAXUNIQHUDID - 1))
                        CON_ERRPRINTF("invalid value %d", (int)tw);
                    else
                        guniqhudid = tw;

                    dispatch();
                }

            vInstruction(CON_SAVEGAMEVAR):
            vInstruction(CON_READGAMEVAR):
            {
                int32_t nValue = 0;
                insptr++;
                if (ud.config.scripthandle < 0)
                {
                    insptr++;
                    dispatch();
                }
                switch (VM_DECODE_INST(tw))
                {
                    case CON_SAVEGAMEVAR:
                        nValue = Gv_GetVar(*insptr);
                        SCRIPT_PutNumber(ud.config.scripthandle, "Gamevars", aGameVars[*insptr++].szLabel, nValue, FALSE, FALSE);
                        break;
                    case CON_READGAMEVAR:
                        SCRIPT_GetNumber(ud.config.scripthandle, "Gamevars", aGameVars[*insptr].szLabel, &nValue);
                        Gv_SetVar(*insptr++, nValue);
                        break;
                }
                dispatch();
            }

            vInstruction(CON_SHOWVIEW):
            vInstruction(CON_SHOWVIEWUNBIASED):
            vInstruction(CON_SHOWVIEWQ16):
            vInstruction(CON_SHOWVIEWQ16UNBIASED):
                insptr++;
                {
                    struct
                    {
                        vec3_t  vec;
                        int32_t params[3];
                        vec2_t  scrn[2];
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((v.scrn[0].x < 0) | (v.scrn[0].y < 0) | (v.scrn[1].x >= 320) | (v.scrn[1].y >= 200), "invalid coordinates");
                    VM_ABORT_IF((unsigned)v.params[2] >= MAXSECTORS, "invalid sector %d", v.params[2]);

                    if (VM_DECODE_INST(tw) != CON_SHOWVIEWQ16 && VM_DECODE_INST(tw) != CON_SHOWVIEWQ16UNBIASED)
                    {
                        v.params[0] <<= 16;
                        v.params[1] <<= 16;
                    }

                    G_ShowView(v.vec, v.params[0], v.params[1], v.params[2], v.scrn[0].x, v.scrn[0].y, v.scrn[1].x, v.scrn[1].y,
                               (VM_DECODE_INST(tw) != CON_SHOWVIEW && VM_DECODE_INST(tw) != CON_SHOWVIEWQ16));

                    dispatch();
                }

            vInstruction(CON_ROTATESPRITEA):
            vInstruction(CON_ROTATESPRITE16):
            vInstruction(CON_ROTATESPRITE):
                insptr++;
                {
                    struct
                    {
                        vec3_t  pos;
                        int32_t ang, tilenum, shade, pal, orientation;
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((unsigned)v.tilenum >= MAXTILES, "invalid tilenum %d", v.tilenum);

                    int32_t alpha = (VM_DECODE_INST(tw) == CON_ROTATESPRITEA) ? Gv_GetVar(*insptr++) : 0;
                    vec2_t bound[2];
                    Gv_FillWithVars(bound);

                    if (VM_DECODE_INST(tw) != CON_ROTATESPRITE16 && !(v.orientation & ROTATESPRITE_FULL16))
                    {
                        v.pos.x <<= 16;
                        v.pos.y <<= 16;
                    }

                    int32_t blendidx = 0;

                    NEG_ALPHA_TO_BLEND(alpha, blendidx, v.orientation);

                    rotatesprite_(v.pos.x, v.pos.y, v.pos.z, v.ang, v.tilenum, v.shade, v.pal, 2 | (v.orientation & (ROTATESPRITE_MAX - 1)), alpha,
                                  blendidx, bound[0].x, bound[0].y, bound[1].x, bound[1].y);
                    dispatch();
                }

            vInstruction(CON_GAMETEXT):
            vInstruction(CON_GAMETEXTZ):
                insptr++;
                {
                    struct
                    {
                        int32_t tilenum;
                        vec2_t  pos;
                        int32_t nQuote, shade, pal, orientation;
                        vec2_t  bound[2];
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((v.tilenum < 0) | (v.tilenum + 127 >= MAXTILES), "invalid base tilenum %d", v.tilenum);
                    VM_ABORT_IF(bad_quote(v.nQuote), "invalid quote %d", v.nQuote);

                    int32_t const z = (VM_DECODE_INST(tw) == CON_GAMETEXTZ) ? Gv_GetVar(*insptr++) : 65536;

                    G_PrintGameText(v.tilenum, v.pos.x >> 1, v.pos.y, apStrings[v.nQuote], v.shade, v.pal, v.orientation & (ROTATESPRITE_MAX - 1),
                                    v.bound[0].x, v.bound[0].y, v.bound[1].x, v.bound[1].y, z, 0);
                    dispatch();
                }

            vInstruction(CON_DIGITALNUMBER):
            vInstruction(CON_DIGITALNUMBERZ):
                insptr++;
                {
                    struct
                    {
                        int32_t tilenum;
                        vec2_t  pos;
                        int32_t nQuote, shade, pal, orientation;
                        vec2_t  bound[2];
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((v.tilenum < 0) | (v.tilenum + 9 >= MAXTILES), "invalid base tilenum %d", v.tilenum);

                    int32_t const nZoom = (VM_DECODE_INST(tw) == CON_DIGITALNUMBERZ) ? Gv_GetVar(*insptr++) : 65536;

                    // NOTE: '-' not taken into account, but we have rotatesprite() bound check now anyway

                    G_DrawTXDigiNumZ(v.tilenum, v.pos.x, v.pos.y, v.nQuote, v.shade, v.pal, v.orientation & (ROTATESPRITE_MAX - 1), v.bound[0].x,
                                     v.bound[0].y, v.bound[1].x, v.bound[1].y, nZoom);
                    dispatch();
                }

            vInstruction(CON_MINITEXT):
                insptr++;
                {
                    struct
                    {
                        vec2_t  pos;
                        int32_t nQuote, shade, pal;
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF(bad_quote(v.nQuote), "invalid quote %d", v.nQuote);

                    minitextshade(v.pos.x, v.pos.y, apStrings[v.nQuote], v.shade, v.pal, 2 + 8 + 16);
                    dispatch();
                }

            vInstruction(CON_SCREENTEXT):
                insptr++;
                {
                    struct
                    {
                        int32_t tilenum;
                        vec3_t  v;
                        int32_t blockangle, charangle, nQuote, shade, pal, orientation, alpha;
                        vec2_t  spacing, between;
                        int32_t nFlags;
                        vec2_t  bound[2];
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((v.tilenum < 0) | (v.tilenum + 127 >= MAXTILES), "invalid base tilenum %d", v.tilenum);
                    VM_ABORT_IF(bad_quote(v.nQuote), "invalid quote %d", v.nQuote);

                    if (!(v.orientation & ROTATESPRITE_FULL16))
                    {
                        v.v.x <<= 16;
                        v.v.y <<= 16;
                        v.spacing.x <<= 16;
                        v.spacing.y <<= 16;
                        v.between.x <<= 16;
                        v.between.y <<= 16;
                    }

                    G_SetScreenTextEmpty(v.spacing, v.tilenum, v.nFlags);

                    G_ScreenText(v.tilenum, v.v.x, v.v.y, v.v.z, v.blockangle, v.charangle, apStrings[v.nQuote], v.shade, v.pal,
                                 2 | (v.orientation & (ROTATESPRITE_MAX - 1)), v.alpha, v.spacing.x, v.spacing.y, v.between.x, v.between.y, v.nFlags,
                                 v.bound[0].x, v.bound[0].y, v.bound[1].x, v.bound[1].y);
                    dispatch();
                }

            vInstruction(CON_GETZRANGE):
                insptr++;
                {
                    struct
                    {
                        vec3_t  vect;
                        int32_t sectNum;
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((unsigned)v.sectNum >= MAXSECTORS, "invalid sector %d", v.sectNum);

                    int const ceilzvar   = *insptr++;
                    int const ceilhitvar = *insptr++;
                    int const florzvar   = *insptr++;
                    int const florhitvar = *insptr++;

                    struct
                    {
                        int32_t walldist, clipmask;
                    } v2;
                    Gv_FillWithVars(v2);

                    int32_t ceilz, ceilhit, florz, florhit;

                    getzrange(&v.vect, v.sectNum, &ceilz, &ceilhit, &florz, &florhit, v2.walldist, v2.clipmask);
                    Gv_SetVar(ceilzvar, ceilz);
                    Gv_SetVar(ceilhitvar, ceilhit);
                    Gv_SetVar(florzvar, florz);
                    Gv_SetVar(florhitvar, florhit);

                    dispatch();
                }

            vInstruction(CON_SECTSETINTERPOLATION):
            vInstruction(CON_SECTCLEARINTERPOLATION):
                insptr++;
                {
                    int const sectnum = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((unsigned)sectnum >= MAXSECTORS, "invalid sector %d", sectnum);

                    if (VM_DECODE_INST(tw) == CON_SECTSETINTERPOLATION)
                        Sect_SetInterpolation(sectnum);
                    else
                        Sect_ClearInterpolation(sectnum);

                    dispatch();
                }

            vInstruction(CON_CALCHYPOTENUSE):
                insptr++;
                {
                    int32_t returnVar = *insptr++;
                    vec2_t  da;
                    Gv_FillWithVars(da);
                    int64_t const hypsq = (int64_t)da.x * da.x + (int64_t)da.y * da.y;

                    Gv_SetVar(returnVar, (hypsq > (int64_t)INT32_MAX) ? (int32_t)sqrt((double)hypsq) : ksqrt((uint32_t)hypsq));
                    dispatch();
                }

            vInstruction(CON_LINEINTERSECT):
            vInstruction(CON_RAYINTERSECT):
                insptr++;
                {
                    struct
                    {
                        vec3_t vec[2];
                        vec2_t vec2[2];
                    } v;
                    Gv_FillWithVars(v);

                    int const intxvar = *insptr++;
                    int const intyvar = *insptr++;
                    int const intzvar = *insptr++;
                    int const retvar  = *insptr++;
                    vec3_t    in;

                    int ret = ((VM_DECODE_INST(tw) == CON_LINEINTERSECT) ? lintersect : rayintersect)(v.vec[0].x, v.vec[0].y, v.vec[0].z, v.vec[1].x, v.vec[1].y,
                                                                                v.vec[1].z, v.vec2[0].x, v.vec2[0].y, v.vec2[1].x, v.vec2[1].y,
                                                                                &in.x, &in.y, &in.z);

                    Gv_SetVar(retvar, ret);

                    if (ret)
                    {
                        Gv_SetVar(intxvar, in.x);
                        Gv_SetVar(intyvar, in.y);
                        Gv_SetVar(intzvar, in.z);
                    }

                    dispatch();
                }

            vInstruction(CON_CLIPMOVE):
            vInstruction(CON_CLIPMOVENOSLIDE):
                insptr++;
                {
                    typedef struct
                    {
                        int32_t w, f, c;
                    } vec3dist_t;

                    int const returnVar = *insptr++;
                    int const xReturn   = *insptr++;
                    int const yReturn   = *insptr++;

                    insptr -= 2;

                    typedef struct
                    {
                        vec3_t     vec3;
                        int32_t    sectNum32;
                        vec2_t     vec2;
                        vec3dist_t dist;
                        int32_t    clipMask;
                    } clipmoveparams_t;

                    int32_t const sectReturn = insptr[offsetof(clipmoveparams_t, sectNum32) / sizeof(int32_t)];

                    clipmoveparams_t v;
                    Gv_FillWithVars(v);

                    int16_t sectNum = v.sectNum32;
                    VM_ABORT_IF((unsigned)sectNum >= MAXSECTORS, "invalid sector %d", sectNum);

                    Gv_SetVar(
                    returnVar,
                    clipmovex(&v.vec3, &sectNum, v.vec2.x, v.vec2.y, v.dist.w, v.dist.f, v.dist.c, v.clipMask, (VM_DECODE_INST(tw) == CON_CLIPMOVENOSLIDE)));
                    Gv_SetVar(sectReturn, v.sectNum32);
                    Gv_SetVar(xReturn, v.vec3.x);
                    Gv_SetVar(yReturn, v.vec3.y);

                    dispatch();
                }

            vInstruction(CON_HITSCAN):
                insptr++;
                {
                    struct
                    {
                        vec3_t  origin;
                        int32_t sectnum;
                        vec3_t  vect;
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((unsigned)v.sectnum >= MAXSECTORS, "invalid sector %d", v.sectnum);

                    int const sectReturn   = *insptr++;
                    int const wallReturn   = *insptr++;
                    int const spriteReturn = *insptr++;
                    int const xReturn      = *insptr++;
                    int const yReturn      = *insptr++;
                    int const zReturn      = *insptr++;
                    int const clipType     = Gv_GetVar(*insptr++);

                    hitdata_t hit;
                    hitscan(&v.origin, v.sectnum, v.vect.x, v.vect.y, v.vect.z, &hit, clipType);

                    Gv_SetVar(sectReturn, hit.sect);
                    Gv_SetVar(wallReturn, hit.wall);
                    Gv_SetVar(spriteReturn, hit.sprite);
                    Gv_SetVar(xReturn, hit.x);
                    Gv_SetVar(yReturn, hit.y);
                    Gv_SetVar(zReturn, hit.z);
                    dispatch();
                }

            vInstruction(CON_CANSEE):
                insptr++;
                {
                    struct
                    {
                        vec3_t  vec1;
                        int32_t firstSector;
                        vec3_t  vec2;
                        int32_t secondSector;
                    } v;
                    Gv_FillWithVars(v);

                    int32_t returnvar = *insptr++;
                    int32_t wallmask = Gv_GetVar(*insptr++);

                    VM_ABORT_IF(((unsigned)v.firstSector >= (unsigned)numsectors) | ((unsigned)v.secondSector >= (unsigned)numsectors), "invalid sector %d",
                              (unsigned)v.firstSector >= (unsigned)numsectors ? v.firstSector : v.secondSector);

                    Gv_SetVar(returnvar, cansee(v.vec1.x, v.vec1.y, v.vec1.z, v.firstSector, v.vec2.x, v.vec2.y, v.vec2.z, v.secondSector, wallmask));
                    dispatch();
                }

            vInstruction(CON_ROTATEPOINT):
                insptr++;
                {
                    struct
                    {
                        vec2_t  point[2];
                        int32_t angle;
                    } v;
                    Gv_FillWithVars(v);

                    int const xReturn = *insptr++;
                    int const yReturn = *insptr++;
                    vec2_t    result;

                    rotatepoint(v.point[0], v.point[1], v.angle, &result);

                    Gv_SetVar(xReturn, result.x);
                    Gv_SetVar(yReturn, result.y);
                    dispatch();
                }

            vInstruction(CON_NEARTAG):
                insptr++;
                {
                    //             neartag(int32_t x, int32_t y, int32_t z, short sectnum, short ang,  //Starting position & angle
                    //                     short *neartagsector,   //Returns near sector if sector[].tag != 0
                    //                     short *neartagwall,     //Returns near wall if wall[].tag != 0
                    //                     short *neartagsprite,   //Returns near sprite if sprite[].tag != 0
                    //                     int32_t *neartaghitdist,   //Returns actual distance to object (scale: 1024=largest grid size)
                    //                     int32_t neartagrange,      //Choose maximum distance to scan (scale: 1024=largest grid size)
                    //                     char tagsearch)         //1-lotag only, 2-hitag only, 3-lotag&hitag

                    struct
                    {
                        vec3_t  point;
                        int32_t sectNum, nAngle;
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((unsigned)v.sectNum >= MAXSECTORS, "invalid sector %d", v.sectNum);

                    int const sectReturn   = *insptr++;
                    int const wallReturn   = *insptr++;
                    int const spriteReturn = *insptr++;
                    int const distReturn   = *insptr++;

                    struct
                    {
                        int32_t tagRange, tagSearch;
                    } v2;
                    Gv_FillWithVars(v2);

                    int16_t neartagsector, neartagwall, neartagsprite;
                    int32_t neartaghitdist;

                    neartag(v.point.x, v.point.y, v.point.z, v.sectNum, v.nAngle, &neartagsector, &neartagwall, &neartagsprite, &neartaghitdist,
                            v2.tagRange, v2.tagSearch, NULL);

                    Gv_SetVar(sectReturn, neartagsector);
                    Gv_SetVar(wallReturn, neartagwall);
                    Gv_SetVar(spriteReturn, neartagsprite);
                    Gv_SetVar(distReturn, neartaghitdist);
                    dispatch();
                }

            vInstruction(CON_GETTIMEDATE):
                insptr++;
                {
                    int32_t values[8];
                    G_GetTimeDate(values);

                    for (int value : values)
                        Gv_SetVar(*insptr++, value);

                    dispatch();
                }

            vInstruction(CON_MOVESPRITE):
                insptr++;
                {
                    struct
                    {
                        int32_t spriteNum;
                        vec3_t  vect;
                        int32_t clipType;
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((unsigned)v.spriteNum >= MAXSPRITES, "invalid sprite %d", v.spriteNum);

                    Gv_SetVar(*insptr++, A_MoveSprite(v.spriteNum, v.vect, v.clipType));
                    dispatch();
                }

            vInstruction(CON_SETSPRITE):
                insptr++;
                {
                    struct
                    {
                        int32_t spriteNum;
                        vec3_t  vect;
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((unsigned)v.spriteNum >= MAXSPRITES, "invalid sprite %d", v.spriteNum);

                    setsprite(v.spriteNum, &v.vect);
                    dispatch();
                }

            vInstruction(CON_GETFLORZOFSLOPE):
            vInstruction(CON_GETCEILZOFSLOPE):
                insptr++;
                {
                    struct
                    {
                        int32_t sectNum;
                        vec2_t  vect;
                    } v;
                    Gv_FillWithVars(v);

                    VM_ABORT_IF((unsigned)v.sectNum >= MAXSECTORS, "invalid sector %d", v.sectNum);

                    Gv_SetVar(*insptr++, (VM_DECODE_INST(tw) == CON_GETFLORZOFSLOPE ? yax_getflorzofslope : yax_getceilzofslope)(v.sectNum, v.vect));
                    dispatch();
                }

            vInstruction(CON_UPDATESECTOR):
                insptr++;
                {
                    vec2_t vect = { 0, 0 };
                    Gv_FillWithVars(vect);

                    int const returnVar = *insptr++;
                    int16_t   sectNum   = Gv_GetVar(returnVar);

                    if ((unsigned)sectNum >= MAXSECTORS)
                        sectNum = vm.pSprite->sectnum;

                    updatesector(vect.x, vect.y, &sectNum);
                    Gv_SetVar(returnVar, sectNum);
                    dispatch();
                }

            vInstruction(CON_UPDATESECTORZ):
                insptr++;
                {
                    vec3_t vect = { 0, 0, 0 };
                    Gv_FillWithVars(vect);

                    int const returnVar = *insptr++;
                    int16_t   sectNum   = Gv_GetVar(returnVar);

                    if ((unsigned)sectNum >= MAXSECTORS)
                        sectNum = vm.pSprite->sectnum;

                    updatesectorz(vect.x, vect.y, vect.z, &sectNum);
                    Gv_SetVar(returnVar, sectNum);
                    dispatch();
                }

            vInstruction(CON_UPDATESECTORNEIGHBOR):
                insptr++;
                {
                    vec2_t vect = { 0, 0 };
                    Gv_FillWithVars(vect);

                    int const returnVar = *insptr++;
                    int16_t   sectNum   = Gv_GetVar(returnVar);

                    if ((unsigned)sectNum >= MAXSECTORS)
                        sectNum = vm.pSprite->sectnum;

                    updatesectorneighbor(vect.x, vect.y, &sectNum, getsectordist(vect, sectNum));
                    Gv_SetVar(returnVar, sectNum);
                    dispatch();
                }

            vInstruction(CON_UPDATESECTORNEIGHBORZ):
                insptr++;
                {
                    vec3_t vect = { 0, 0, 0 };
                    Gv_FillWithVars(vect);

                    int const returnVar = *insptr++;
                    int16_t   sectNum   = Gv_GetVar(returnVar);

                    if ((unsigned)sectNum >= MAXSECTORS)
                        sectNum = vm.pSprite->sectnum;

                    updatesectorneighborz(vect.x, vect.y, vect.z, &sectNum, getsectordist({vect.x, vect.y}, sectNum));
                    Gv_SetVar(returnVar, sectNum);
                    dispatch();
                }

            vInstruction(CON_CAPIA):
            {
                insptr++;
                int const nQuote = Gv_GetVar(*insptr++);

                VM_ABORT_IF(bad_quote(nQuote), "invalid quote %d", nQuote);

                communityapiUnlockAchievement(apStrings[nQuote]);
                dispatch();
            }

            vInstruction(CON_CAPIS):
            {
                insptr++;
                int const nQuote = Gv_GetVar(*insptr++);
                int const value = Gv_GetVar(*insptr++);

                VM_ABORT_IF(bad_quote(nQuote), "invalid quote %d", nQuote);

                communityapiSetStat(apStrings[nQuote], value);
                dispatch();
            }

            vInstruction(CON_SPAWN):
                insptr++;

                VM_ABORT_IF((unsigned)vm.pSprite->sectnum >= MAXSECTORS, "invalid sector %d", vm.pUSprite->sectnum);

                A_Spawn(vm.spriteNum, *insptr++);
                dispatch();

            vInstruction(CON_RESETACTIONCOUNT):
                insptr++;
                AC_ACTION_COUNT(vm.pData) = 0;
                dispatch();

            vInstruction(CON_DEBRIS):
                insptr++;
                {
#ifndef EDUKE32_STANDALONE
                    if (!FURY)
                    {
                        int debrisTile = *insptr++;

                        if ((unsigned)vm.pSprite->sectnum < MAXSECTORS)
                            for (native_t cnt = (*insptr) - 1; cnt >= 0; cnt--)
                            {
                                int const tileOffset = (vm.pSprite->picnum == BLIMP && debrisTile == SCRAP1) ? 0 : (krand() % 3);

                                int const spriteNum = A_InsertSprite(
                                vm.pSprite->sectnum, vm.pSprite->x + (krand() & 255) - 128, vm.pSprite->y + (krand() & 255) - 128,
                                vm.pSprite->z - (8 << 8) - (krand() & 8191), debrisTile + tileOffset, vm.pSprite->shade, 32 + (krand() & 15),
                                32 + (krand() & 15), krand() & 2047, (krand() & 127) + 32, -(krand() & 2047), vm.spriteNum, 5);

                                sprite[spriteNum].yvel = (vm.pSprite->picnum == BLIMP && debrisTile == SCRAP1) ? g_blimpSpawnItems[cnt % 14] : -1;
                                sprite[spriteNum].pal  = vm.pSprite->pal;
                            }
                    }
#else
                    insptr++;
#endif
                    insptr++;
                }
                dispatch();

            vInstruction(CON_COUNT):
                insptr++;
                AC_COUNT(vm.pData) = (int16_t)*insptr++;
                dispatch();

            vInstruction(CON_CSTATOR):
                insptr++;
                vm.pSprite->cstat |= (int16_t)*insptr++;
                dispatch();

            vInstruction(CON_CLIPDIST):
                insptr++;
                vm.pSprite->clipdist = (int16_t)*insptr++;
                dispatch();

            vInstruction(CON_CSTAT):
                insptr++;
                vm.pSprite->cstat = (int16_t)*insptr++;
                dispatch();

            vInstruction(CON_SAVENN):
            vInstruction(CON_SAVE):
                insptr++;
                {
                    int32_t const requestedSlot = *insptr++;

                    if ((unsigned)requestedSlot >= 10)
                        dispatch();

                    // check if we need to make a new file
                    if (strcmp(g_lastautosave.path, g_lastusersave.path) == 0 || requestedSlot != g_lastAutoSaveArbitraryID)
                    {
                        g_lastautosave.reset();
                    }

                    g_lastAutoSaveArbitraryID = requestedSlot;

                    if (VM_DECODE_INST(tw) == CON_SAVE || g_lastautosave.name[0] == 0)
                    {
                        time_t     timeStruct = time(NULL);
                        struct tm *pTime      = localtime(&timeStruct);

                        strftime(g_lastautosave.name, sizeof(g_lastautosave.name), "%d %b %Y %I:%M%p", pTime);
                    }

                    g_saveRequested = true;

                    dispatch();
                }

            vInstruction(CON_QUAKE):
                insptr++;
                g_earthquakeTime = Gv_GetVar(*insptr++);
#ifndef EDUKE32_STANDALONE
                if (!FURY)
                    A_PlaySound(EARTHQUAKE, g_player[screenpeek].ps->i);
#endif
                dispatch();

            vInstruction(CON_RESETPLAYER):
                insptr++;
                vm.flags = VM_ResetPlayer(vm.playerNum, vm.flags, 0);
                dispatch();

            vInstruction(CON_RESETPLAYERFLAGS):
                insptr++;
                vm.flags = VM_ResetPlayer(vm.playerNum, vm.flags, Gv_GetVar(*insptr++));
                dispatch();

            vInstruction(CON_RESETCOUNT):
                insptr++;
                AC_COUNT(vm.pData) = 0;
                dispatch();

            vInstruction(CON_ADDINVENTORY):
                insptr += 2;

                VM_AddInventory(vm.pPlayer, insptr[-1], *insptr);

                insptr++;
                dispatch();

            vInstruction(CON_HITRADIUS):
                insptr++;
                {
                    int32_t params[5];
                    Gv_FillWithVars(params);
                    A_RadiusDamage(vm.spriteNum, params[0], params[1], params[2], params[3], params[4]);
                }
                dispatch();

            vInstruction(CON_IFP):
            {
                int const moveFlags  = *(++insptr);
                int       nResult    = 0;
                int const playerXVel = sprite[vm.pPlayer->i].xvel;
                int const syncBits   = g_player[vm.playerNum].input.bits;

                if (((moveFlags & pducking) && vm.pPlayer->on_ground && TEST_SYNC_KEY(syncBits, SK_CROUCH))
                    || ((moveFlags & pfalling) && vm.pPlayer->jumping_counter == 0 && !vm.pPlayer->on_ground && vm.pPlayer->vel.z > 2048)
                    || ((moveFlags & pjumping) && vm.pPlayer->jumping_counter > 348)
                    || ((moveFlags & pstanding) && playerXVel >= 0 && playerXVel < 8)
                    || ((moveFlags & pwalking) && playerXVel >= 8 && !TEST_SYNC_KEY(syncBits, SK_RUN))
                    || ((moveFlags & prunning) && playerXVel >= 8 && TEST_SYNC_KEY(syncBits, SK_RUN))
                    || ((moveFlags & phigher) && vm.pPlayer->pos.z < (vm.pSprite->z - (48 << 8)))
                    || ((moveFlags & pwalkingback) && playerXVel <= -8 && !TEST_SYNC_KEY(syncBits, SK_RUN))
                    || ((moveFlags & prunningback) && playerXVel <= -8 && TEST_SYNC_KEY(syncBits, SK_RUN))
                    || ((moveFlags & pkicking)
                        && (vm.pPlayer->quick_kick > 0
                            || (PWEAPON(vm.playerNum, vm.pPlayer->curr_weapon, WorksLike) == KNEE_WEAPON && vm.pPlayer->kickback_pic > 0)))
                    || ((moveFlags & pshrunk) && sprite[vm.pPlayer->i].xrepeat < 32)
                    || ((moveFlags & pjetpack) && vm.pPlayer->jetpack_on)
                    || ((moveFlags & ponsteroids) && vm.pPlayer->inv_amount[GET_STEROIDS] > 0 && vm.pPlayer->inv_amount[GET_STEROIDS] < 400)
                    || ((moveFlags & ponground) && vm.pPlayer->on_ground)
                    || ((moveFlags & palive) && sprite[vm.pPlayer->i].xrepeat > 32 && sprite[vm.pPlayer->i].extra > 0 && vm.pPlayer->timebeforeexit == 0)
                    || ((moveFlags & pdead) && sprite[vm.pPlayer->i].extra <= 0))
                    nResult = 1;
                else if ((moveFlags & pfacing))
                {
                    nResult
                    = (vm.pSprite->picnum == APLAYER && (g_netServer || ud.multimode > 1))
                      ? G_GetAngleDelta(fix16_to_int(g_player[otherp].ps->q16ang),
                                        getangle(vm.pPlayer->pos.x - g_player[otherp].ps->pos.x, vm.pPlayer->pos.y - g_player[otherp].ps->pos.y))
                      : G_GetAngleDelta(fix16_to_int(vm.pPlayer->q16ang), getangle(vm.pSprite->x - vm.pPlayer->pos.x, vm.pSprite->y - vm.pPlayer->pos.y));

                    nResult = (nResult > -128 && nResult < 128);
                }
                branch(nResult);
            }
                dispatch();

            vInstruction(CON_GUTS):
#ifndef EDUKE32_STANDALONE
                if (!FURY)
                    A_DoGuts(vm.spriteNum, insptr[1], insptr[2]);
#endif
                insptr += 3;
                dispatch();

            vInstruction(CON_WACKPLAYER):
                insptr++;
                P_ForceAngle(vm.pPlayer);
                dispatch();

            vInstruction(CON_FLASH):
                insptr++;
                vm.pSprite->shade      = -127;
                vm.pPlayer->visibility = -127;
                dispatch();

            vInstruction(CON_SAVEMAPSTATE):
                insptr++;
                VM_ABORT_IF((g_player[myconnectindex].ps->gm & (MODE_EOL | MODE_RESTART | MODE_GAME)) != MODE_GAME,
                        "attempted to save state in invalid gamemode: %d", g_player[myconnectindex].ps->gm);
                G_SaveMapState();
                dispatch();

            vInstruction(CON_LOADMAPSTATE):
                G_RestoreMapState();
                insptr++;
                dispatch();

            vInstruction(CON_CLEARMAPSTATE):
                insptr++;
                {
                    int const levelNum = Gv_GetVar(*insptr++);
                    VM_ABORT_IF((unsigned)levelNum >= MAXVOLUMES * MAXLEVELS, "invalid map number %d", levelNum);
                    G_FreeMapState(levelNum);
                }
                dispatch();

            vInstruction(CON_STOPALLSOUNDS):
                insptr++;
                if (screenpeek == vm.playerNum)
                    FX_StopAllSounds();
                dispatch();

            vInstruction(CON_STOPALLMUSIC):
                insptr++;
                S_StopMusic();
                dispatch();

            vInstruction(CON_OPERATE):
                insptr++;
                if (sector[vm.pSprite->sectnum].lotag == 0)
                {
                    int16_t foundSect, foundWall, foundSprite;
                    int32_t foundDist;

                    neartag(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - ZOFFSET5, vm.pSprite->sectnum, vm.pSprite->ang, &foundSect, &foundWall,
                            &foundSprite, &foundDist, 768, 4 + 1, NULL);

                    if (foundSect >= 0 && isanearoperator(sector[foundSect].lotag))
                        if ((sector[foundSect].lotag & 0xff) == ST_23_SWINGING_DOOR || sector[foundSect].floorz == sector[foundSect].ceilingz)
                            if ((sector[foundSect].lotag & (16384u | 32768u)) == 0)
                            {
                                int32_t j;

                                for (SPRITES_OF_SECT(foundSect, j))
                                    if (sprite[j].picnum == ACTIVATOR)
                                        break;

                                if (j == -1)
                                    G_OperateSectors(foundSect, vm.spriteNum);
                            }
                }
                dispatch();

            vInstruction(CON_SPRITEPAL):
                insptr++;
                if (vm.pSprite->picnum != APLAYER)
                    vm.pActor->tempang = vm.pSprite->pal;
                vm.pSprite->pal = *insptr++;
                dispatch();

            vInstruction(CON_CACTOR):
                insptr++;
                vm.pSprite->picnum = *insptr++;
                dispatch();

            vInstruction(CON_PALFROM):
                insptr++;
                VM_ABORT_IF((unsigned)vm.playerNum >= MAXPLAYERS, "invalid player %d", vm.playerNum);
                {
                    palette_t const pal = { uint8_t(insptr[1]), uint8_t(insptr[2]), uint8_t(insptr[3]), uint8_t(insptr[0]) };
                    insptr += 4;
                    P_PalFrom(vm.pPlayer, pal.f, pal.r, pal.g, pal.b);
                }
                dispatch();

            vInstruction(CON_SCREENPAL):
                insptr++;
                {
                    int32_t params[4];
                    Gv_FillWithVars(params);
                    videoFadePalette(params[0], params[1], params[2], params[3]);
                }
                dispatch();

            vInstruction(CON_SECTOROFWALL):
                insptr++;
                tw = *insptr++;
                Gv_SetVar(tw, sectorofwall(Gv_GetVar(*insptr++)));
                dispatch();

            vInstruction(CON_QSPRINTF):
                insptr++;
                {
                    int const outputQuote = Gv_GetVar(*insptr++);
                    int const inputQuote  = Gv_GetVar(*insptr++);

                    VM_ABORT_IF((apStrings[inputQuote] == nullptr) | (apStrings[outputQuote] == nullptr), "null quote %d", apStrings[inputQuote] ? outputQuote : inputQuote);

                    auto &inBuf = apStrings[inputQuote];

                    int32_t arg[32];
                    char    outBuf[MAXQUOTELEN];

                    int const quoteLen = Bstrlen(inBuf);

                    int inputPos  = 0;
                    int outputPos = 0;
                    int argIdx    = 0;

                    while (VM_DECODE_INST(*insptr) != CON_NULLOP && argIdx < 32)
                        arg[argIdx++] = Gv_GetVar(*insptr++);

                    int numArgs = argIdx;

                    insptr++;  // skip the NOP

                    argIdx = 0;

                    do
                    {
                        while (inputPos < quoteLen && outputPos < MAXQUOTELEN && inBuf[inputPos] != '%')
                            outBuf[outputPos++] = inBuf[inputPos++];

                        if (inBuf[inputPos] == '%')
                        {
                            inputPos++;
                            switch (inBuf[inputPos])
                            {
                                case 'l':
                                    if (inBuf[inputPos + 1] != 'd')
                                    {
                                        // write the % and l
                                        outBuf[outputPos++] = inBuf[inputPos - 1];
                                        outBuf[outputPos++] = inBuf[inputPos++];
                                        break;
                                    }
                                    inputPos++;
                                    fallthrough__;
                                case 'd':
                                {
                                    if (argIdx >= numArgs)
                                        goto finish_qsprintf;

                                    char buf[16];
                                    Bsprintf(buf, "%d", arg[argIdx++]);

                                    int const bufLen = Bstrlen(buf);
                                    Bmemcpy(&outBuf[outputPos], buf, bufLen);
                                    outputPos += bufLen;
                                    inputPos++;
                                }
                                break;

                                case 's':
                                {
                                    if (argIdx >= numArgs)
                                        goto finish_qsprintf;

                                    int const argLen = Bstrlen(apStrings[arg[argIdx]]);

                                    Bmemcpy(&outBuf[outputPos], apStrings[arg[argIdx]], argLen);
                                    outputPos += argLen;
                                    argIdx++;
                                    inputPos++;
                                }
                                break;

                                default: outBuf[outputPos++] = inBuf[inputPos - 1]; break;
                            }
                        }
                    } while (inputPos < quoteLen && outputPos < MAXQUOTELEN);
                finish_qsprintf:
                    outBuf[outputPos] = '\0';
                    Bstrncpyz(apStrings[outputQuote], outBuf, MAXQUOTELEN);
                    dispatch();
                }

            vInstruction(CON_ADDLOGVAR):
#define AL_CHECK_INDEX(upper)                                        \
    if (EDUKE32_PREDICT_FALSE((unsigned)index >= upper))             \
    {                                                                \
        CON_ERRPRINTF("index %d exceeds limit of %d", index, upper); \
        abort_after_error();                                         \
    }
                insptr++;
                {
                    unsigned int const lVarID = *insptr;
                    int const gv = (lVarID & (MAXGAMEVARS - 1));
                    int const sign = (lVarID & GV_FLAG_NEGATIVE) ? -1 : 1;
                    char    szBuf[256];

                    if (lVarID >= (unsigned) g_gameVarCount)
                    {
                        insptr++;
                        if (lVarID == GV_FLAG_CONSTANT)
                        {
                            auto fn = VM_FILENAME(insptr);
                            OSD_Printf(OSDTEXT_GREEN "%s:%d: addlog %d (constant...)\n", fn, VM_DECODE_LINE_NUMBER(g_tw), (int)*insptr++);
                            dispatch();
                        }
                        else if (lVarID & GV_FLAG_ARRAY)
                        {
                            int index = Gv_GetVar(*insptr++);
                            AL_CHECK_INDEX((unsigned) aGameArrays[gv].size);

                            OSD_Printf(OSDTEXT_GREEN "%s:%d: addlog %s[%d] = %d\n", VM_FILENAME(insptr), VM_DECODE_LINE_NUMBER(g_tw), aGameArrays[gv].szLabel, index,
                                       (int32_t)(sign * Gv_GetArrayValue(gv, index)));
                            dispatch();
                        }
                        else if (lVarID & GV_FLAG_STRUCT)
                        {
                            int const indexVar = *insptr++;
                            int index = Gv_GetVar(indexVar);
                            int const labelNum = *insptr++;
                            int const structType = gv - g_structVarIDs;

                            const char* structName;
                            const char* labelName;
                            int secondaryIndex = -1;
                            int returnValue = -1;
                            switch(structType)
                            {
                                case STRUCT_ACTORVAR:
                                    AL_CHECK_INDEX(MAXSPRITES);
                                    structName = "actorvar";
                                    labelName = aGameVars[labelNum].szLabel;
                                    returnValue = Gv_GetVar(labelNum, index, vm.playerNum);
                                    break;

                                case STRUCT_PLAYERVAR:
                                    if (indexVar == g_thisActorVarID)
                                        index = vm.playerNum;
                                    AL_CHECK_INDEX(MAXPLAYERS);
                                    structName = "playervar";
                                    labelName = aGameVars[labelNum].szLabel;
                                    returnValue = Gv_GetVar(labelNum, vm.spriteNum, index);
                                    break;

                                case STRUCT_SPRITE:
                                case STRUCT_SPRITE_INTERNAL__:
                                    AL_CHECK_INDEX(MAXSPRITES);
                                    structName = "sprite";
                                    labelName = ActorLabels[labelNum].name;
                                    if (structType == STRUCT_SPRITE)
                                    {
                                        secondaryIndex = (ActorLabels[labelNum].flags & LABEL_HASPARM2) ? Gv_GetVar(*insptr++, vm.spriteNum, vm.playerNum) : -1;
                                        returnValue = VM_GetSprite(index, labelNum, (EDUKE32_PREDICT_FALSE(secondaryIndex < 0)) ? 0: secondaryIndex);
                                    }
                                    else
                                        returnValue = VM_GetStruct(ActorLabels[labelNum].flags, (intptr_t *)((intptr_t)&sprite[index] + ActorLabels[labelNum].offset));
                                    break;

                                case STRUCT_ACTOR_INTERNAL__:
                                    AL_CHECK_INDEX(MAXSPRITES);
                                    structName = "actor";
                                    labelName = ActorLabels[labelNum].name;
                                    returnValue = VM_GetStruct(ActorLabels[labelNum].flags, (intptr_t *)((intptr_t)&actor[index] + ActorLabels[labelNum].offset));
                                    break;

                                case STRUCT_SECTOR:
                                case STRUCT_SECTOR_INTERNAL__:
                                    if (indexVar == g_thisActorVarID)
                                        index = vm.pSprite->sectnum;
                                    AL_CHECK_INDEX(MAXSECTORS);
                                    structName = "sector";
                                    labelName = SectorLabels[labelNum].name;
                                    returnValue = (structType == STRUCT_SECTOR) ? VM_GetSector(index, labelNum) : VM_GetStruct(SectorLabels[labelNum].flags,
                                            (intptr_t *)((intptr_t)&sector[index] + SectorLabels[labelNum].offset));
                                    break;

                                case STRUCT_WALL:
                                case STRUCT_WALL_INTERNAL__:
                                    AL_CHECK_INDEX(MAXWALLS);
                                    structName = "wall";
                                    labelName = WallLabels[labelNum].name;
                                    returnValue = (structType == STRUCT_WALL) ? VM_GetWall(index, labelNum) : VM_GetStruct(WallLabels[labelNum].flags,
                                            (intptr_t *)((intptr_t)&wall[index] + WallLabels[labelNum].offset));
                                    break;

                                case STRUCT_SPRITEEXT_INTERNAL__:
                                    AL_CHECK_INDEX(MAXSPRITES);
                                    structName = "spriteext";
                                    labelName = ActorLabels[labelNum].name;
                                    returnValue = VM_GetStruct(ActorLabels[labelNum].flags, (intptr_t *)((intptr_t)&spriteext[index] + ActorLabels[labelNum].offset));
                                    break;

                                case STRUCT_TSPR:
                                    AL_CHECK_INDEX(MAXSPRITES);
                                    structName = "tspr";
                                    labelName = TsprLabels[labelNum].name;
                                    returnValue = VM_GetStruct(TsprLabels[labelNum].flags, (intptr_t *)((intptr_t)(spriteext[index].tspr) + TsprLabels[labelNum].offset));
                                    break;

                                case STRUCT_PLAYER:
                                case STRUCT_PLAYER_INTERNAL__:
                                    if (indexVar == g_thisActorVarID)
                                        index = vm.playerNum;
                                    AL_CHECK_INDEX(MAXPLAYERS);
                                    structName = "player";
                                    labelName = PlayerLabels[labelNum].name;
                                    if (structType == STRUCT_PLAYER)
                                    {
                                        secondaryIndex = (EDUKE32_PREDICT_FALSE(PlayerLabels[labelNum].flags & LABEL_HASPARM2)) ? Gv_GetVar(*insptr++, vm.spriteNum, vm.playerNum) : -1;
                                        returnValue = VM_GetPlayer(index, labelNum, (EDUKE32_PREDICT_FALSE(secondaryIndex < 0)) ? 0: secondaryIndex);
                                    }
                                    else returnValue = VM_GetStruct(PlayerLabels[labelNum].flags, (intptr_t *)((intptr_t)&g_player[index].ps[0] + PlayerLabels[labelNum].offset));
                                    break;

                                case STRUCT_THISPROJECTILE:
                                    AL_CHECK_INDEX(MAXSPRITES);
                                    structName = "thisprojectile";
                                    labelName = ProjectileLabels[labelNum].name;
                                    returnValue = VM_GetActiveProjectile(index, labelNum);
                                    break;

                                case STRUCT_PROJECTILE:
                                    AL_CHECK_INDEX(MAXTILES);
                                    structName = "projectile";
                                    labelName = ProjectileLabels[labelNum].name;
                                    returnValue = VM_GetProjectile(index, labelNum);
                                    break;

                                case STRUCT_TILEDATA:
                                    if (indexVar == g_thisActorVarID)
                                        index = vm.pSprite->picnum;
                                    AL_CHECK_INDEX(MAXTILES);
                                    structName = "tiledata";
                                    labelName = TileDataLabels[labelNum].name;
                                    returnValue = VM_GetTileData(index, labelNum);
                                    break;

                                case STRUCT_PALDATA:
                                    if (indexVar == g_thisActorVarID)
                                        index = vm.pSprite->pal;
                                    AL_CHECK_INDEX(MAXPALOOKUPS);
                                    structName = "paldata";
                                    labelName = PalDataLabels[labelNum].name;
                                    returnValue = VM_GetPalData(index, labelNum);
                                    break;

                                case STRUCT_INPUT:
                                    if (indexVar == g_thisActorVarID)
                                        index = vm.playerNum;
                                    AL_CHECK_INDEX(MAXPLAYERS);
                                    structName = "input";
                                    labelName = InputLabels[labelNum].name;
                                    returnValue = VM_GetPlayerInput(index, labelNum);
                                    break;

                                case STRUCT_USERDEF:
                                    structName = "userdef";
                                    labelName = UserdefsLabels[labelNum].name;
                                    secondaryIndex = (EDUKE32_PREDICT_FALSE(UserdefsLabels[labelNum].flags & LABEL_HASPARM2)) ? Gv_GetVar(*insptr++) : -1;
                                    returnValue   = VM_GetUserdef(labelNum, (EDUKE32_PREDICT_FALSE(secondaryIndex < 0)) ? 0: secondaryIndex);
                                    break;
                                default:
                                    // invalid struct type -- this should never happen
                                    CON_ERRPRINTF("unknown struct type: %d -- This is a bug and should be reported to the engine developers.", structType);
                                    abort_after_error();
                            }

                            Bsprintf(tempbuf, "%s:%d: addlog %s", VM_FILENAME(insptr), VM_DECODE_LINE_NUMBER(g_tw), structName);
                            if (EDUKE32_PREDICT_TRUE(index >= 0))
                            {
                                Bsprintf(szBuf, "[%d]", index);
                                Bstrcat(tempbuf, szBuf);
                            }

                            Bsprintf(szBuf, ".%s", labelName);
                            Bstrcat(tempbuf, szBuf);

                            if (EDUKE32_PREDICT_FALSE(secondaryIndex >= 0))
                            {
                                Bsprintf(szBuf, "[%d]", secondaryIndex);
                                Bstrcat(tempbuf, szBuf);
                            }

                            Bsprintf(szBuf, " = %d", returnValue);
                            Bstrcat(tempbuf, szBuf);
                            OSD_Printf(OSDTEXT_GREEN "%s\n", tempbuf);
                            dispatch();
                        }
                        else
                        {
                            // invalid lVarID -- this should never happen
                            CON_ERRPRINTF("unknown variable type: %d -- This is a bug and should be reported to the engine developers.", lVarID);
                            abort_after_error();
                        }
                    }
                    Bsprintf(tempbuf, "%s:%d: addlog %s", VM_FILENAME(insptr), VM_DECODE_LINE_NUMBER(g_tw), aGameVars[gv].szLabel);

                    if (aGameVars[gv].flags & GAMEVAR_READONLY)
                    {
                        Bsprintf(szBuf, " (read-only...)");
                        Bstrcat(tempbuf, szBuf);
                    }

                    if (aGameVars[gv].flags & GAMEVAR_PERPLAYER)
                        Bsprintf(szBuf, " (player %d)", vm.playerNum);
                    else if (aGameVars[gv].flags & GAMEVAR_PERACTOR)
                        Bsprintf(szBuf, " (sprite %d)", vm.spriteNum);
                    else
                        szBuf[0] = '\0';

                    if (szBuf[0] != '\0')
                        Bstrcat(tempbuf, szBuf);

                    Bsprintf(szBuf, " = %d\n", Gv_GetVar(gv) * sign);
                    Bstrcat(tempbuf, szBuf);

                    OSD_Printf(OSDTEXT_GREEN "%s", tempbuf);
                    insptr++;
                    dispatch();
                }

            vInstruction(CON_SQRT):
                insptr++;
                {
                    // syntax sqrt <invar> <outvar>
                    int const sqrtval = ksqrt((uint32_t)Gv_GetVar(*insptr++));
                    Gv_SetVar(*insptr++, sqrtval);
                    dispatch();
                }

            vInstruction(CON_FINDNEARACTOR):
            vInstruction(CON_FINDNEARSPRITE):
            vInstruction(CON_FINDNEARACTOR3D):
            vInstruction(CON_FINDNEARSPRITE3D):
                insptr++;
                {
                    // syntax findnearactorvar <type> <maxdistvar> <getvar>
                    // gets the sprite ID of the nearest actor within max dist
                    // that is of <type> into <getvar>
                    // -1 for none found
                    // <type> <maxdistvarid> <varid>
                    int const  decodedInst  = VM_DECODE_INST(tw);
                    int const  actorsOnly   = ((decodedInst == CON_FINDNEARACTOR) | (decodedInst == CON_FINDNEARACTOR3D));
                    auto const dist_funcptr = ((decodedInst == CON_FINDNEARACTOR) | (decodedInst == CON_FINDNEARSPRITE)) ? &ldist : &dist;

                    int const findTile  = *insptr++;
                    int       maxDist   = Gv_GetVar(*insptr++);
                    int const returnVar = *insptr++;

                    int findStatnum = actorsOnly ? STAT_ACTOR : MAXSTATUS - 1;
                    int foundSprite = -1;

                    do
                    {
                        int spriteNum = headspritestat[findStatnum];  // all sprites

                        while ((unsigned)spriteNum < MAXSPRITES)
                        {
                            if (sprite[spriteNum].picnum == findTile && spriteNum != vm.spriteNum)
                            {
                                int const foundDist = dist_funcptr(vm.pSprite, &sprite[spriteNum]);

                                if (foundDist < maxDist)
                                {
                                    maxDist     = foundDist;
                                    foundSprite = spriteNum;
                                }
                            }

                            spriteNum = nextspritestat[spriteNum];
                        }

                        if (actorsOnly)
                            break;
                    }
                    while (findStatnum--);

                    Gv_SetVar(returnVar, foundSprite);
                    dispatch();
                }

            vInstruction(CON_FINDNEARACTORZ):
            vInstruction(CON_FINDNEARSPRITEZ):
                insptr++;
                {
                    // syntax findnearactorvar <type> <maxdistvar> <getvar>
                    // gets the sprite ID of the nearest actor within max dist
                    // that is of <type> into <getvar>
                    // -1 for none found
                    // <type> <maxdistvarid> <varid>
                    int const actorsOnly = (VM_DECODE_INST(tw) == CON_FINDNEARACTORZ);

                    int const findTile  = *insptr++;
                    int       maxDist   = Gv_GetVar(*insptr++);
                    int const maxZDist  = Gv_GetVar(*insptr++);
                    int const returnVar = *insptr++;

                    int findStatnum = actorsOnly ? STAT_ACTOR : MAXSTATUS - 1;
                    int foundSprite = -1;

                    do
                    {
                        int spriteNum = headspritestat[findStatnum];  // all sprites

                        while ((unsigned)spriteNum < MAXSPRITES)
                        {
                            if (sprite[spriteNum].picnum == findTile && spriteNum != vm.spriteNum)
                            {
                                int const foundDist = ldist(vm.pSprite, &sprite[spriteNum]);

                                if (foundDist < maxDist && klabs(vm.pSprite->z - sprite[spriteNum].z) < maxZDist)
                                {
                                    maxDist     = foundDist;
                                    foundSprite = spriteNum;
                                }
                            }

                            spriteNum = nextspritestat[spriteNum];
                        }

                        if (actorsOnly)
                            break;
                    }
                    while (findStatnum--);

                    Gv_SetVar(returnVar, foundSprite);
                    dispatch();
                }

            vInstruction(CON_FINDPLAYER):
            {
                int32_t tw;
                insptr++;
                aGameVars[g_returnVarID].global = A_FindPlayer(vm.pSprite, &tw);
                Gv_SetVar(*insptr++, tw);
                dispatch();
            }

            vInstruction(CON_FINDOTHERPLAYER):
            {
                int32_t tw;
                insptr++;
                aGameVars[g_returnVarID].global = P_FindOtherPlayer(vm.playerNum, &tw);
                Gv_SetVar(*insptr++, tw);
                dispatch();
            }

            vInstruction(CON_GETINPUT):
                insptr++;
                {
                    int const playerNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.playerNum;
                    tw = *insptr++;

                    Gv_SetVar(*insptr++, VM_GetPlayerInput(playerNum, tw));
                    dispatch();
                }

            vInstruction(CON_SETINPUT):
                insptr++;
                {
                    int const playerNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.playerNum;
                    tw = *insptr++;

                    VM_SetPlayerInput(playerNum, tw, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETUSERDEF):
                insptr++;
                {
                    tw = *insptr++;
                    int const lParm2 = (UserdefsLabels[tw].flags & LABEL_HASPARM2) ? Gv_GetVar(*insptr++) : 0;

                    Gv_SetVar(*insptr++, VM_GetUserdef(tw, lParm2));
                    dispatch();
                }

            vInstruction(CON_GETTILEDATA):
                insptr++;
                {
                    int const tileNum  = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.pSprite->picnum;
                    tw = *insptr++;

                    Gv_SetVar(*insptr++, VM_GetTileData(tileNum, tw));
                    dispatch();
                }

            vInstruction(CON_SETTILEDATA):
                insptr++;
                {
                    int const tileNum  = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(insptr[-1]) : vm.pSprite->picnum;
                    tw = *insptr++;

                    VM_SetTileData(tileNum, tw, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_SETUSERDEF):
                insptr++;
                {
                    tw = *insptr++;
                    int const lParm2 = (UserdefsLabels[tw].flags & LABEL_HASPARM2) ? Gv_GetVar(*insptr++) : 0;

                    VM_SetUserdef(tw, lParm2, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETPROJECTILE):
                insptr++;
                {
                    tw = Gv_GetVar(*insptr++);
                    int const labelNum = *insptr++;
                    Gv_SetVar(*insptr++, VM_GetProjectile(tw, labelNum));
                    dispatch();
                }

            vInstruction(CON_SETPROJECTILE):
                insptr++;
                {
                    tw = Gv_GetVar(*insptr++);
                    int const labelNum = *insptr++;
                    VM_SetProjectile(tw, labelNum, Gv_GetVar(*insptr++));
                    dispatch();
                }

            vInstruction(CON_GETANGLETOTARGET):
                insptr++;
                // vm.pActor->lastvx and lastvy are last known location of target.
                Gv_SetVar(*insptr++, getangle(vm.pActor->lastv.x - vm.pSprite->x, vm.pActor->lastv.y - vm.pSprite->y));
                dispatch();

            vInstruction(CON_ANGOFF):
                insptr++;
                spriteext[vm.spriteNum].mdangoff = Gv_GetVar(*insptr++);
                dispatch();

            vInstruction(CON_LOCKPLAYER):
                insptr++;
                vm.pPlayer->transporter_hold = Gv_GetVar(*insptr++);
                dispatch();

            vInstruction(CON_CHECKAVAILWEAPON):
            vInstruction(CON_CHECKAVAILINVEN):
            {
                insptr++;
                int const playerNum = (*insptr++ != g_thisActorVarID) ? Gv_GetVar(*insptr) : vm.playerNum;

                VM_ABORT_IF((unsigned)playerNum >= MAXPLAYERS, "invalid player %d", playerNum);

                if (VM_DECODE_INST(tw) == CON_CHECKAVAILWEAPON)
                    P_CheckWeapon(g_player[playerNum].ps);
                else
                    P_SelectNextInvItem(g_player[playerNum].ps);

                dispatch();
            }

            vInstruction(CON_GETPLAYERANGLE):
                insptr++;
                Gv_SetVar(*insptr++, fix16_to_int(vm.pPlayer->q16ang));
                dispatch();

            vInstruction(CON_GETACTORANGLE):
                insptr++;
                Gv_SetVar(*insptr++, vm.pSprite->ang);
                dispatch();

            vInstruction(CON_SETPLAYERANGLE):
                insptr++;
                vm.pPlayer->q16ang = fix16_from_int(Gv_GetVar(*insptr++) & 2047);
                dispatch();

            vInstruction(CON_SETACTORANGLE):
                insptr++;
                vm.pSprite->ang = Gv_GetVar(*insptr++) & 2047;
                dispatch();

            vInstruction(CON_KLABS):
                if ((aGameVars[insptr[1]].flags & (GAMEVAR_USER_MASK | GAMEVAR_PTR_MASK)) == 0)
                    aGameVars[insptr[1]].global = klabs(aGameVars[insptr[1]].global);
                else
                    Gv_SetVar(insptr[1], klabs(Gv_GetVar(insptr[1])));
                insptr += 2;
                dispatch();

            vInstruction(CON_SETARRAY):
                insptr++;
                {
                    tw = *insptr++;
                    int const arrayIndex = Gv_GetVar(*insptr++);
                    VM_ABORT_IF(((unsigned)tw >= (unsigned)g_gameArrayCount) | ((unsigned)arrayIndex >= (unsigned)aGameArrays[tw & (MAXGAMEARRAYS-1)].size),
                              "invalid array %d or index %d", tw, arrayIndex);

                    SetArray(tw, arrayIndex, Gv_GetVar(*insptr++));

                    dispatch();
                }

            vInstruction(CON_GETARRAYSEQUENCE):
            {
                insptr++;
                int32_t const arrayNum = *insptr++;
                int32_t const arraySize = (aGameArrays[arrayNum].flags & GAMEARRAY_VARSIZE) ? Gv_GetVar(aGameArrays[arrayNum].size) : aGameArrays[arrayNum].size;
                int32_t const sequenceSize = *insptr++;
                int32_t const copySize = min(sequenceSize, arraySize); // warning?
                auto const insptrbak = insptr;

                for (int arrayIndex = 0; arrayIndex < copySize; ++arrayIndex)
                    Gv_SetVar(*insptr++, Gv_GetArrayValue(arrayNum, arrayIndex));

                insptr = insptrbak + sequenceSize;
                dispatch();
            }

            vInstruction(CON_SETARRAYSEQUENCE):
            {
                insptr++;
                int32_t const arrayNum = *insptr++;
                int32_t const sequenceSize = *insptr++;

                VM_ABORT_IF((unsigned)arrayNum >= (unsigned)g_gameArrayCount, "invalid array %d", arrayNum);

                ResizeArray(arrayNum, sequenceSize);

                for (int arrayIndex = 0; arrayIndex < sequenceSize; ++arrayIndex)
                    SetArray(arrayNum, arrayIndex, Gv_GetVar(*insptr++));

                dispatch();
            }

            vInstruction(CON_READARRAYFROMFILE):
                insptr++;
                {
                    int const arrayNum      = *insptr++;
                    int const quoteFilename = *insptr++;

                    VM_ABORT_IF(bad_quote(quoteFilename), "invalid quote %d", quoteFilename);

                    buildvfs_kfd kFile = kopen4loadfrommod(apStrings[quoteFilename], 0);

                    if (kFile == buildvfs_kfd_invalid)
                        dispatch();

                    size_t const filelength  = kfilelength(kFile);
                    size_t const numElements = Gv_GetArrayCountForAllocSize(arrayNum, filelength);

                    if (numElements > 0)
                    {
                        size_t const newBytes  = Gv_GetArrayAllocSizeForCount(arrayNum, numElements);
                        size_t const readBytes = min(newBytes, filelength);
                        size_t const oldBytes  = Gv_GetArrayAllocSize(arrayNum);

                        intptr_t *&pValues = aGameArrays[arrayNum].pValues;

                        if (newBytes != oldBytes)
                        {
                            Xaligned_free(pValues);
                            pValues = (intptr_t *)Xaligned_alloc(ARRAY_ALIGNMENT, newBytes);
                        }

                        aGameArrays[arrayNum].size = numElements;

                        uintptr_t const flags = aGameArrays[arrayNum].flags;

                        switch (flags & GAMEARRAY_SIZE_MASK)
                        {
                            case 0:
#ifdef BITNESS64
                            {
                                void *const pArray = Xcalloc(1, newBytes);

                                kread(kFile, pArray, readBytes);

                                if (flags & GAMEARRAY_UNSIGNED)
                                {
                                    for (unative_t i = 0; i < numElements; ++i)
                                        pValues[i]   = ((uint32_t *)pArray)[i];
                                }
                                else
                                {
                                    for (unative_t i = 0; i < numElements; ++i)
                                        pValues[i]   = ((int32_t *)pArray)[i];
                                }

                                Xfree(pArray);
                                break;
                            }
#endif
                            default:
                                memset((char *)pValues + readBytes, 0, newBytes - readBytes);
                                kread(kFile, pValues, readBytes);
                                break;
                        }
                    }

                    kclose(kFile);
                    dispatch();
                }

            vInstruction(CON_WRITEARRAYTOFILE):
                insptr++;
                {
                    int const arrayNum      = *insptr++;
                    int const quoteFilename = *insptr++;

                    VM_ABORT_IF(bad_quote(quoteFilename), "invalid quote %d", quoteFilename);

                    char temp[BMAX_PATH];

                    if (EDUKE32_PREDICT_FALSE(G_ModDirSnprintf(temp, sizeof(temp), "%s", apStrings[quoteFilename])))
                    {
                        LOG_F(ERROR, "Unable to save array to %s: resulting filename is too long.", apStrings[quoteFilename]);
                        abort_after_error();
                    }

                    buildvfs_FILE const fil = buildvfs_fopen_write(temp);

                    if (EDUKE32_PREDICT_FALSE(fil == NULL))
                    {
                        LOG_F(ERROR, "Unable to save array to %s: unknown error.", temp);
                        abort_after_error();
                    }

                    switch (aGameArrays[arrayNum].flags & GAMEARRAY_SIZE_MASK)
                    {
                        case 0:
#ifdef BITNESS64
                        {
                            size_t const   numElements  = aGameArrays[arrayNum].size;
                            size_t const   numDiskBytes = numElements * sizeof(int32_t);
                            int32_t *const pArray       = (int32_t *)Xmalloc(numDiskBytes);

                            for (unative_t k = 0; k < numElements; ++k)
                                pArray[k]    = Gv_GetArrayValue(arrayNum, k);

                            buildvfs_fwrite(pArray, 1, numDiskBytes, fil);
                            Xfree(pArray);
                            break;
                        }
#endif
                        default: buildvfs_fwrite(aGameArrays[arrayNum].pValues, 1, Gv_GetArrayAllocSize(arrayNum), fil); break;
                    }

                    buildvfs_fclose(fil);
                    dispatch();
                }

            vInstruction(CON_GETARRAYSIZE):
                insptr++;
                tw = *insptr++;
                Gv_SetVar(*insptr++, (aGameArrays[tw].flags & GAMEARRAY_VARSIZE) ? Gv_GetVar(aGameArrays[tw].size) : aGameArrays[tw].size);
                dispatch();

            vInstruction(CON_RESIZEARRAY):
                insptr++;
                {
                    tw = *insptr++;
                    int const newSize = Gv_GetVar(*insptr++);
                    ResizeArray(tw, newSize);

                    dispatch();
                }

            vInstruction(CON_COPY):
                insptr++;
                {
                    int const srcArray       = *insptr++;
                    int       srcArrayIndex  = Gv_GetVar(*insptr++);  //, vm.spriteNum, vm.playerNum);
                    int const destArray      = *insptr++;
                    int       destArrayIndex = Gv_GetVar(*insptr++);
                    int       numElements    = Gv_GetVar(*insptr++);

                    auto &src = aGameArrays[srcArray];
                    auto &dest = aGameArrays[destArray];

                    int const srcArraySize = (src.flags & GAMEARRAY_VARSIZE) ? Gv_GetVar(src.size) : src.size;
                    int const destArraySize = (dest.flags & GAMEARRAY_VARSIZE) ? Gv_GetVar(dest.size) : dest.size;

                    if ((srcArrayIndex > srcArraySize) | (destArrayIndex > destArraySize))
                        dispatch();

                    if ((srcArrayIndex + numElements) > srcArraySize)
                        numElements = srcArraySize - srcArrayIndex;

                    if ((destArrayIndex + numElements) > destArraySize)
                        numElements = destArraySize - destArrayIndex;

                    // Switch depending on the source array type.

                    int const srcInc  = 1 << (int)!!((src.flags & GAMEARRAY_STRIDE2));
                    int const destInc = 1 << (int)!!((dest.flags & GAMEARRAY_STRIDE2));

                    // matching array types, no BITMAPs, no STRIDE2 flag
                    if ((src.flags & GAMEARRAY_SIZE_MASK) == (dest.flags & GAMEARRAY_SIZE_MASK)
                        && !((src.flags | dest.flags) & GAMEARRAY_BITMAP) && (srcInc & destInc) == 1)
                    {
                        Bmemcpy(dest.pValues + destArrayIndex, src.pValues + srcArrayIndex,
                                numElements * Gv_GetArrayElementSize(srcArray));
                        dispatch();
                    }

                    switch (dest.flags & GAMEARRAY_TYPE_MASK)
                    {
                        case 0:
                            for (; numElements > 0; --numElements)
                            {
                                dest.pValues[destArrayIndex] = Gv_GetArrayValue(srcArray, srcArrayIndex++);
                                destArrayIndex += destInc;
                            }
                            break;
                        case GAMEARRAY_INT16:
                            for (; numElements > 0; --numElements)
                            {
                                ((int16_t *)dest.pValues)[destArrayIndex] = Gv_GetArrayValue(srcArray, srcArrayIndex++);
                                destArrayIndex += destInc;
                            }
                            break;
                        case GAMEARRAY_INT8:
                            for (; numElements > 0; --numElements)
                            {
                                ((int8_t *)dest.pValues)[destArrayIndex] = Gv_GetArrayValue(srcArray, srcArrayIndex++);
                                destArrayIndex += destInc;
                            }
                            break;
                        case GAMEARRAY_UINT16:
                            for (; numElements > 0; --numElements)
                            {
                                ((uint16_t *)dest.pValues)[destArrayIndex] = Gv_GetArrayValue(srcArray, srcArrayIndex++);
                                destArrayIndex += destInc;
                            }
                            break;
                        case GAMEARRAY_UINT8:
                            for (; numElements > 0; --numElements)
                            {
                                ((uint8_t *)dest.pValues)[destArrayIndex] = Gv_GetArrayValue(srcArray, srcArrayIndex++);
                                destArrayIndex += destInc;
                            }
                            break;
                        case GAMEARRAY_BITMAP:
                            for (; numElements > 0; --numElements)
                            {
                                uint32_t const newValue = Gv_GetArrayValue(srcArray, srcArrayIndex++);
                                uint32_t const mask = 1 << (destArrayIndex & 7);
                                uint8_t & value = ((uint8_t *)dest.pValues)[destArrayIndex >> 3];
                                value = (value & ~mask) | (-!!newValue & mask);
                                destArrayIndex += destInc;
                            }
                            break;
                    }

                    dispatch();
                }

            vInstruction(CON_SWAPARRAYS):
                insptr++;
                {
                    auto &array1 = aGameArrays[*insptr++];
                    auto &array2 = aGameArrays[*insptr++];

                    swap(&array1.size, &array2.size);
                    swap(&array1.pValues, &array2.pValues);

                    dispatch();
                }

            vInstruction(CON_GETNGCFLAGS):
            vInstruction(CON_SETNGCFLAGS):
                insptr++;
                {
                    int vm_inst = VM_DECODE_INST(tw);
                    intptr_t destVar;
                    int newFlags;

                    if (vm_inst == CON_GETNGCFLAGS)
                        destVar = *insptr++;
                    else
                        newFlags = Gv_GetVar(*insptr++);

                    int i = 0, ngcIdx[4] = {0};
                    int newIndex = ngcIdx[0] = Gv_GetVar(*insptr++);
                    if (EDUKE32_PREDICT_FALSE((unsigned)newIndex >= MAXMENUGAMEPLAYENTRIES))
                    {
                        CON_ERRPRINTF("Attempting to access undefined ngc menu entry on layer 0: %d", newIndex);
                        abort_after_error();
                    }

                    MenuGameplayEntry* cmg = &g_MenuGameplayEntries[newIndex];
                    for (; i < MAXMENUGAMEPLAYLAYERS; i++)
                    {
                        if (*insptr == VM_VSIZE_LINE_END)
                        {
                            insptr++;
                            break;
                        }

                        newIndex = Gv_GetVar(*insptr++);
                        ngcIdx[i+1] = newIndex;

                        if (EDUKE32_PREDICT_FALSE((!cmg->subentries) | ((unsigned)newIndex >= MAXMENUGAMEPLAYENTRIES)))
                        {
                            CON_ERRPRINTF("Attempting to access undefined ngc menu entry on layer %d: %d", i+1, newIndex);
                            abort_after_error();
                        }
                        cmg = &cmg->subentries[newIndex];
                    }

                    if (vm_inst == CON_GETNGCFLAGS)
                    {
                        switch(i)
                        {
                            case 0:
                                Gv_SetVar(destVar, ME_NEWGAMECUSTOMENTRIES[ngcIdx[0]].flags);
                                break;
                            case 1:
                                Gv_SetVar(destVar, ME_NEWGAMECUSTOMSUBENTRIES[ngcIdx[0]][ngcIdx[1]].flags);
                                break;
                            default:
                                Gv_SetVar(destVar, ME_NEWGAMECUSTOML3ENTRIES[ngcIdx[0]][ngcIdx[1]][ngcIdx[2]].flags);
                                break;
                        }
                    }
                    else
                    {
                        switch(i)
                        {
                            case 0:
                                ME_NEWGAMECUSTOMENTRIES[ngcIdx[0]].flags = newFlags;
                                break;
                            case 1:
                                ME_NEWGAMECUSTOMSUBENTRIES[ngcIdx[0]][ngcIdx[1]].flags = newFlags;
                                break;
                            default:
                                ME_NEWGAMECUSTOML3ENTRIES[ngcIdx[0]][ngcIdx[1]][ngcIdx[2]].flags = newFlags;
                                break;
                        }
                    }
                }
                dispatch();

            vInstruction(CON_DISPLAYRANDVAR):
                insptr++;
                Gv_SetVar(*insptr, mulscale15(system_15bit_rand(), insptr[1] + 1));
                insptr += 2;
                dispatch();

            vInstruction(CON_CLAMP):
                insptr++;
                {
                    tw = *insptr++;
                    int const min = Gv_GetVar(*insptr++);
                    Gv_SetVar(tw, clamp2(Gv_GetVar(tw), min, Gv_GetVar(*insptr++)));
                }
                dispatch();

            vInstruction(CON_GETCLOSESTCOL):
                insptr++;
                {
                    tw = *insptr++;
                    int32_t const rgb = Gv_GetVar(*insptr++);
                    Gv_SetVar(tw, paletteGetClosestColorUpToIndex(rgb & 0xFF, (rgb >> 8) & 0xFF, (rgb >> 16) & 0xFF, Gv_GetVar(*insptr++)));
                }
                dispatch();

            vInstruction(CON_DRAWLINE256):
                insptr++;
                {
                    struct
                    {
                        vec2_t  pos[2];
                        int32_t index;
                    } v;

                    Gv_FillWithVars(v);

                    renderDrawLine(v.pos[0].x, v.pos[0].y, v.pos[1].x, v.pos[1].y, v.index);
                }
                dispatch();

            vInstruction(CON_DRAWLINERGB):
                insptr++;
                {
                    struct
                    {
                        vec2_t  pos[2];
                        int32_t index, rgb;
                    } v;

                    Gv_FillWithVars(v);

                    palette_t const p
                    = { (uint8_t)(v.rgb & 0xFF), (uint8_t)((v.rgb >> 8) & 0xFF), (uint8_t)((v.rgb >> 16) & 0xFF), (uint8_t)v.index };

                    drawlinergb(v.pos[0].x, v.pos[0].y, v.pos[1].x, v.pos[1].y, p);
                }
                dispatch();

            vInstruction(CON_INV):
                if ((aGameVars[insptr[1]].flags & (GAMEVAR_USER_MASK | GAMEVAR_PTR_MASK)) == 0)
                    aGameVars[insptr[1]].global = -aGameVars[insptr[1]].global;
                else
                    Gv_SetVar(insptr[1], -Gv_GetVar(insptr[1]));
                insptr += 2;
                dispatch();

            vInstruction(CON_DISPLAYRANDVARVAR):
                insptr++;
                tw = *insptr++;
                Gv_SetVar(tw, mulscale15(system_15bit_rand(), Gv_GetVar(*insptr++) + 1));
                dispatch();

            vInstruction(CON_GMAXAMMO):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                VM_ABORT_IF((unsigned)tw >= MAX_WEAPONS, "invalid weapon %d", (int)tw);
                Gv_SetVar(*insptr++, vm.pPlayer->max_ammo_amount[tw]);
                dispatch();

            vInstruction(CON_SMAXAMMO):
                insptr++;
                tw = Gv_GetVar(*insptr++);
                VM_ABORT_IF((unsigned)tw >= MAX_WEAPONS, "invalid weapon %d", (int)tw);
                vm.pPlayer->max_ammo_amount[tw] = Gv_GetVar(*insptr++);
                dispatch();

            vInstruction(CON_DIVR):  // div round to nearest
                insptr++;
                {
                    tw = *insptr++;

                    int const dividend = Gv_GetVar(tw);
                    int const divisor  = Gv_GetVar(*insptr++);

                    if (EDUKE32_PREDICT_FALSE(!divisor))
                    {
                        CON_CRITICALERRPRINTF("divide by zero!");
                        abort_after_error();
                    }

                    Gv_SetVar(tw, tabledivide32((dividend + ksgn(dividend) * klabs(divisor / 2)), divisor));
                    dispatch();
                }

            vInstruction(CON_DIVRU):  // div round away from zero
                insptr++;
                {
                    tw = *insptr++;

                    int const dividend = Gv_GetVar(tw);
                    int const divisor  = Gv_GetVar(*insptr++);

                    if (EDUKE32_PREDICT_FALSE(!divisor))
                    {
                        CON_CRITICALERRPRINTF("divide by zero!");
                        abort_after_error();
                    }

                    Gv_SetVar(tw, tabledivide32((dividend + ksgn(dividend) * klabs(divisor) + 1), divisor));
                    dispatch();
                }

            vInstruction(CON_SIN):
                insptr++;
                tw = *insptr++;
                Gv_SetVar(tw, sintable[Gv_GetVar(*insptr++) & 2047]);
                dispatch();

            vInstruction(CON_COS):
                insptr++;
                tw = *insptr++;
                Gv_SetVar(tw, sintable[(Gv_GetVar(*insptr++) + 512) & 2047]);
                dispatch();

            vInstruction(CON_SPGETLOTAG):
                insptr++;
                aGameVars[g_lotagVarID].global = vm.pSprite->lotag;
                dispatch();

            vInstruction(CON_SPGETHITAG):
                insptr++;
                aGameVars[g_hitagVarID].global = vm.pSprite->hitag;
                dispatch();

            vInstruction(CON_SECTGETLOTAG):
                insptr++;
                aGameVars[g_lotagVarID].global = sector[vm.pSprite->sectnum].lotag;
                dispatch();

            vInstruction(CON_SECTGETHITAG):
                insptr++;
                aGameVars[g_hitagVarID].global = sector[vm.pSprite->sectnum].hitag;
                dispatch();

            vInstruction(CON_GETTEXTUREFLOOR):
                insptr++;
                aGameVars[g_textureVarID].global = sector[vm.pSprite->sectnum].floorpicnum;
                dispatch();

            vInstruction(CON_STARTTRACK):
                insptr++;
                G_StartTrackSlotWrap(ud.volume_number, Gv_GetVar(*(insptr++)));
                dispatch();

            vInstruction(CON_STARTTRACKSLOT):
                insptr++;
                {
                    int const volumeNum = Gv_GetVar(*(insptr++));
                    int const levelNum  = Gv_GetVar(*(insptr++));
                    G_StartTrackSlotWrap(volumeNum == -1 ? MAXVOLUMES : volumeNum, levelNum);
                }
                dispatch();

            vInstruction(CON_SWAPTRACKSLOT):
                insptr++;
                {
                    int const volumeNum = Gv_GetVar(*(insptr++));
                    int const levelNum  = Gv_GetVar(*(insptr++));

                    if (volumeNum == ud.music_episode && levelNum == ud.music_level)
                        dispatch();

                    // This is the best ASS can do right now. Better implementation pending.
                    int32_t position = S_GetMusicPosition();
                    if (!G_StartTrackSlotWrap(volumeNum == -1 ? MAXVOLUMES : volumeNum, levelNum))
                        S_SetMusicPosition(position);
                }
                dispatch();

            vInstruction(CON_PRELOADTRACKSLOTFORSWAP):
                // ASS can't even handle this command right now.
                insptr++;
                Gv_GetVar(*(insptr++));
                Gv_GetVar(*(insptr++));
                dispatch();

            vInstruction(CON_SETMUSICPOSITION):
                insptr++;
                S_SetMusicPosition(Gv_GetVar(*insptr++));
                dispatch();
            vInstruction(CON_GETMUSICPOSITION):
                insptr++;
                Gv_SetVar(*insptr++, S_GetMusicPosition());
                dispatch();

            vInstruction(CON_ACTIVATECHEAT):
                insptr++;
                tw = Gv_GetVar(*(insptr++));
                VM_ABORT_IF((numplayers != 1) | ((g_player[myconnectindex].ps->gm & MODE_GAME) != MODE_GAME), "not in a single-player game.");
                osdcmd_cheatsinfo_stat.cheatnum = tw;
                dispatch();

            vInstruction(CON_SETGAMEPALETTE):
                insptr++;
                P_SetGamePalette(vm.pPlayer, Gv_GetVar(*(insptr++)), 2 + 16);
                dispatch();

            vInstruction(CON_GETTEXTURECEILING):
                insptr++;
                aGameVars[g_textureVarID].global = sector[vm.pSprite->sectnum].ceilingpicnum;
                dispatch();

            vInstruction(CON_IFPHEALTHL):
                insptr++;
                branch(sprite[vm.pPlayer->i].extra < *insptr);
                dispatch();

            vInstruction(CON_IFPINVENTORY):
                insptr++;

                switch (*insptr++)
                {
                    case GET_STEROIDS:
                    case GET_SHIELD:
                    case GET_SCUBA:
                    case GET_HOLODUKE:
                    case GET_HEATS:
                    case GET_FIRSTAID:
                    case GET_BOOTS:
                    case GET_JETPACK: tw = (vm.pPlayer->inv_amount[insptr[-1]] != *insptr); break;

                    case GET_ACCESS:
                        switch (vm.pSprite->pal)
                        {
                            case 0: tw  = (vm.pPlayer->got_access & 1); break;
                            case 21: tw = (vm.pPlayer->got_access & 2); break;
                            case 23: tw = (vm.pPlayer->got_access & 4); break;
                        }
                        break;
                    default: tw = 0; CON_ERRPRINTF("invalid inventory item %d", (int32_t) * (insptr - 1));
                        dispatch();
                }

                branch(tw);
                dispatch();

            vInstruction(CON_PSTOMP):
                insptr++;
                if (vm.pPlayer->knee_incs == 0 && sprite[vm.pPlayer->i].xrepeat >= 40)
                    if (cansee(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - ZOFFSET6, vm.pSprite->sectnum, vm.pPlayer->pos.x, vm.pPlayer->pos.y,
                               vm.pPlayer->pos.z + ZOFFSET2, sprite[vm.pPlayer->i].sectnum))
                    {
                        int numPlayers = g_mostConcurrentPlayers - 1;

                        for (; numPlayers >= 0; --numPlayers)
                        {
                            if (g_player[numPlayers].ps->actorsqu == vm.spriteNum)
                                break;
                        }

                        if (numPlayers == -1)
                        {
                            if (vm.pPlayer->weapon_pos == 0)
                                vm.pPlayer->weapon_pos = -1;

                            vm.pPlayer->actorsqu  = vm.spriteNum;
                            vm.pPlayer->knee_incs = 1;
                        }
                    }
                dispatch();

            vInstruction(CON_IFAWAYFROMWALL):
            {
                int16_t otherSectnum = vm.pSprite->sectnum;
                tw = 0;

#define IFAWAYDIST 108

                updatesector(vm.pSprite->x + IFAWAYDIST, vm.pSprite->y + IFAWAYDIST, &otherSectnum);
                if (otherSectnum == vm.pSprite->sectnum)
                {
                    updatesector(vm.pSprite->x - IFAWAYDIST, vm.pSprite->y - IFAWAYDIST, &otherSectnum);
                    if (otherSectnum == vm.pSprite->sectnum)
                    {
                        updatesector(vm.pSprite->x + IFAWAYDIST, vm.pSprite->y - IFAWAYDIST, &otherSectnum);
                        if (otherSectnum == vm.pSprite->sectnum)
                        {
                            updatesector(vm.pSprite->x - IFAWAYDIST, vm.pSprite->y + IFAWAYDIST, &otherSectnum);
                            if (otherSectnum == vm.pSprite->sectnum)
                                tw = 1;
                        }
                    }
                }

                branch(tw);

#undef IFAWAYDIST
            }
                dispatch();

            vInstruction(CON_QUOTE):
                insptr++;

                VM_ABORT_IF((unsigned)vm.playerNum >= MAXPLAYERS, "invalid player %d", vm.playerNum);

                P_DoQuote(*(insptr++) | MAXQUOTES, vm.pPlayer);
                dispatch();

            vInstruction(CON_USERQUOTE):
                insptr++;
                tw = Gv_GetVar(*insptr++);

                VM_ABORT_IF(bad_quote(tw), "invalid quote %d", (int)tw);

                G_AddUserQuote(apStrings[tw]);
                dispatch();

            vInstruction(CON_ECHO):
                insptr++;
                tw = Gv_GetVar(*insptr++);

                VM_ABORT_IF(bad_quote(tw), "invalid quote %d", (int)tw);
                VLOG_F(LOG_VM, "%s", apStrings[tw]);
                dispatch();

            vInstruction(CON_RESPAWNHITAG):
                insptr++;
                switch (tileGetMapping(vm.pSprite->picnum))
                {
#ifndef EDUKE32_STANDALONE
                    case FEM1__:
                    case FEM2__:
                    case FEM3__:
                    case FEM4__:
                    case FEM5__:
                    case FEM6__:
                    case FEM7__:
                    case FEM8__:
                    case FEM9__:
                    case FEM10__:
                    case PODFEM1__:
                    case NAKED1__:
                    case STATUE__:
                        if (!FURY)
                        {
                            if (vm.pSprite->yvel)
                                G_OperateRespawns(vm.pSprite->yvel);
                            break;
                        }
                        fallthrough__;
#endif
                    default:
                        if (vm.pSprite->hitag >= 0)
                            G_OperateRespawns(vm.pSprite->hitag);
                        break;
                }
                dispatch();

            vInstruction(CON_IFSPRITEPAL):
                insptr++;
                branch(vm.pSprite->pal == *insptr);
                dispatch();

            vInstruction(CON_IFANGDIFFL):
                insptr++;
                tw = klabs(G_GetAngleDelta(fix16_to_int(vm.pPlayer->q16ang), vm.pSprite->ang));
                branch(tw <= *insptr);
                dispatch();

            vInstruction(CON_IFNOSOUNDS): branch(!A_CheckAnySoundPlaying(vm.spriteNum)); dispatch();

            vInstruction(CON_SPRITEFLAGS):
                insptr++;
                vm.pActor->flags = Gv_GetVar(*insptr++);
                dispatch();

            vInstruction(CON_GETTICKS):
                insptr++;
                Gv_SetVar(*insptr++, timerGetTicks());
                dispatch();

            vInstruction(CON_GETCURRADDRESS):
                insptr++;
                tw = *insptr++;
                Gv_SetVar(tw, (intptr_t)(insptr - apScript));
                dispatch();

            vmErrorCase: // you're not supposed to be here
                VM_ScriptInfo(insptr, 64);
                debug_break();
                G_GameExit("An error has occurred in the " APPNAME " virtual machine.\n\n"
                           "If you are an end user, please e-mail the file " APPBASENAME ".log\n"
                           "along with links to any mods you're using to development@voidpoint.com.\n\n"
                           "If you are a developer, please attach all of your script files\n"
                           "along with instructions on how to reproduce this error.\n\n"
                           "Thank you!");
        }
#ifndef CON_USE_COMPUTED_GOTO
    }
    while (vm_execution_depth && (vm.flags & (VM_RETURN|VM_KILL|VM_NOEXECUTE)) == 0);
#endif
}

// NORECURSE
void A_LoadActor(int const spriteNum)
{
    vm.spriteNum = spriteNum;           // Sprite ID
    vm.pSprite   = &sprite[spriteNum];  // Pointer to sprite structure
    vm.pActor    = &actor[spriteNum];

    if (g_tile[vm.pSprite->picnum].loadPtr == NULL)
        return;

    vm.pData      = &actor[spriteNum].t_data[0];  // Sprite's 'extra' data
    vm.playerNum  = -1;                           // Player ID
    vm.playerDist = -1;                           // Distance
    vm.pPlayer    = g_player[0].ps;

    vm.flags &= ~(VM_RETURN|VM_KILL|VM_NOEXECUTE);

    if ((unsigned)vm.pSprite->sectnum >= MAXSECTORS)
    {
        A_DeleteSprite(vm.spriteNum);
        return;
    }

    insptr = g_tile[vm.pSprite->picnum].loadPtr;
    VM_Execute(true);
    insptr = NULL;

    if (vm.flags & VM_KILL)
        A_DeleteSprite(vm.spriteNum);
}

void VM_UpdateAnim(int const spriteNum, int32_t * const pData)
{
    size_t const actionofs = AC_ACTION_ID(pData);
    auto const   actionptr = &apScript[actionofs];

    if ((actionofs == 0) | (actionofs + (ACTION_PARAM_COUNT-1) >= (unsigned) g_scriptSize))
        return;

    int const action_frames = actionptr[ACTION_NUMFRAMES];
    int const action_incval = actionptr[ACTION_INCVAL];
    int const action_delay  = actionptr[ACTION_DELAY];
    auto actionticsptr = &AC_ACTIONTICS(&sprite[spriteNum], &actor[spriteNum]);
    *actionticsptr += TICSPERFRAME;

    if (*actionticsptr > action_delay)
    {
        *actionticsptr = 0;
        AC_ACTION_COUNT(pData)++;
        AC_CURFRAME(pData) += action_incval;
    }

    if (klabs(AC_CURFRAME(pData)) >= klabs(action_frames * action_incval))
        AC_CURFRAME(pData) = 0;
}

// NORECURSE
void A_Execute(int const spriteNum, int const playerNum, int const playerDist)
{
    // for some reason this is faster than using the C++ syntax; e.g vm = vmstate_t{ ... }
    vmstate_t const tempvm
    = { spriteNum, playerNum, playerDist, 0, &sprite[spriteNum], &actor[spriteNum].t_data[0], g_player[playerNum].ps, &actor[spriteNum] };
    vm = tempvm;

    int const picnum = vm.pSprite->picnum;

    MICROPROFILE_SCOPE_TOKEN(g_actorTokens[picnum]);

    if (g_tileLabels[picnum])
        ERROR_CONTEXT("actor", &g_tileLabels[picnum][0]);
    else
        ERROR_CONTEXT("actor", picnum);

    ERROR_CONTEXT("spritenum", spriteNum);

    //    Bsprintf(tempbuf, "%s (%d)", g_tileLabels[picnum], picnum);
    //else
    //    Bsprintf(tempbuf, "unnamed (%d)", picnum);

#ifndef NETCODE_DISABLE
    if (g_netClient)
    {
#if 0
        if (A_CheckSpriteFlags(spriteNum, SFLAG_NULL))
        {
            A_DeleteSprite(spriteNum);
            return;
        }
#endif
        // [75] The server should not overwrite its own randomseed
        randomseed = ticrandomseed;
    }
#endif

    if (EDUKE32_PREDICT_FALSE((unsigned)vm.pSprite->sectnum >= MAXSECTORS))
    {
        if (A_CheckEnemySprite(vm.pSprite))
            P_AddKills(vm.pPlayer, 1);

        A_DeleteSprite(vm.spriteNum);
        return;
    }

    VM_UpdateAnim(vm.spriteNum, vm.pData);

    insptr = 4 + (g_tile[vm.pSprite->picnum].execPtr);
    VM_Execute(true);
    insptr = NULL;

    if ((vm.flags & VM_KILL) == 0)
    {
        VM_Move();

        if (vm.pSprite->statnum == STAT_ACTOR)
        {
            if (A_CheckEnemySprite(vm.pSprite))
            {
                if (vm.pSprite->xrepeat > 60 || (ud.respawn_monsters == 1 && vm.pSprite->extra <= 0))
                    return;
            }
            else if ((ud.respawn_items == 1 && (vm.pSprite->cstat & 32768)))
                return;

            if (A_CheckSpriteFlags(vm.spriteNum, SFLAG_USEACTIVATOR) && sector[vm.pSprite->sectnum].lotag & 16384)
                changespritestat(vm.spriteNum, STAT_ZOMBIEACTOR);
            else if (vm.pActor->timetosleep > 1)
                vm.pActor->timetosleep--;
            else if (vm.pActor->timetosleep == 1)
            {
                // hack for 1.3D fire sprites
#ifndef EDUKE32_STANDALONE
                if (!FURY && EDUKE32_PREDICT_FALSE(g_scriptVersion == 13 && ((int)(vm.pSprite->picnum == FIRE) | (int)(vm.pSprite->picnum == FIRE2))))
                    return;
#endif
                changespritestat(vm.spriteNum, STAT_ZOMBIEACTOR);
            }
        }
#ifndef EDUKE32_STANDALONE
        else if (!FURY && vm.pSprite->statnum == STAT_STANDABLE)
        {
            switch (tileGetMapping(vm.pSprite->picnum))
            {
                case RUBBERCAN__:
                case EXPLODINGBARREL__:
                case WOODENHORSE__:
                case HORSEONSIDE__:
                case CANWITHSOMETHING__:
                case FIREBARREL__:
                case NUKEBARREL__:
                case NUKEBARRELDENTED__:
                case NUKEBARRELLEAKED__:
                case TRIPBOMB__:
                case EGG__:
                    if (vm.pActor->timetosleep > 1)
                        vm.pActor->timetosleep--;
                    else if (vm.pActor->timetosleep == 1)
                        changespritestat(vm.spriteNum, STAT_ZOMBIEACTOR);
                default:
                    break;
            }
        }
#endif
    }
    else VM_DeleteSprite(spriteNum, playerNum);

    // g_frameJustDrawn is set by G_DrawFrame() (and thus by the coroutine)
    // it isn't cleared until the next game tic is processed.
    dukeMaybeDrawFrame();
}

void G_SaveMapState(void)
{
    int const    levelNum = ud.volume_number * MAXLEVELS + ud.level_number;
    map_t *const pMapInfo = &g_mapInfo[levelNum];

    if (pMapInfo->savedstate == NULL)
    {
        pMapInfo->savedstate = (mapstate_t *) Xaligned_alloc(ACTOR_VAR_ALIGNMENT, sizeof(mapstate_t));
        Bmemset(pMapInfo->savedstate, 0, sizeof(mapstate_t));
    }

    mapstate_t *save = pMapInfo->savedstate;

    save->numwalls = numwalls;
    Bmemcpy(&save->wall[0],&wall[0],sizeof(walltype)*MAXWALLS);
    save->numsectors = numsectors;
    Bmemcpy(&save->sector[0],&sector[0],sizeof(sectortype)*MAXSECTORS);
    Bmemcpy(&save->sprite[0],&sprite[0],sizeof(spritetype)*MAXSPRITES);

    // If we're in EVENT_ANIMATESPRITES, we'll be saving pointer values to disk :-/
    if (EDUKE32_PREDICT_FALSE(g_currentEvent == EVENT_ANIMATESPRITES))
        LOG_F(ERROR, "CON_SAVEMAPSTATE called from EVENT_ANIMATESPRITES. WHY?");

    Bmemcpy(&save->spriteext[0],&spriteext[0],sizeof(spriteext_t)*MAXSPRITES);
#ifndef NEW_MAP_FORMAT
    Bmemcpy(&save->wallext[0],&wallext[0],sizeof(wallext_t)*MAXWALLS);
#endif

    save->numsprites = Numsprites;
    save->tailspritefree = tailspritefree;
    Bmemcpy(&save->headspritesect[0],&headspritesect[0],sizeof(headspritesect));
    Bmemcpy(&save->prevspritesect[0],&prevspritesect[0],sizeof(prevspritesect));
    Bmemcpy(&save->nextspritesect[0],&nextspritesect[0],sizeof(nextspritesect));
    Bmemcpy(&save->headspritestat[0],&headspritestat[0],sizeof(headspritestat));
    Bmemcpy(&save->prevspritestat[0],&prevspritestat[0],sizeof(prevspritestat));
    Bmemcpy(&save->nextspritestat[0],&nextspritestat[0],sizeof(nextspritestat));
#ifdef YAX_ENABLE
    save->numyaxbunches = numyaxbunches;
# if !defined NEW_MAP_FORMAT
    Bmemcpy(save->yax_bunchnum, yax_bunchnum, sizeof(yax_bunchnum));
    Bmemcpy(save->yax_nextwall, yax_nextwall, sizeof(yax_nextwall));
# endif
#endif
    Bmemcpy(&save->actor[0],&actor[0],sizeof(actor_t)*MAXSPRITES);

    save->g_cyclerCnt = g_cyclerCnt;
    Bmemcpy(save->g_cyclers, g_cyclers, sizeof(g_cyclers));
    Bmemcpy(&save->g_playerSpawnPoints[0],&g_playerSpawnPoints[0],sizeof(g_playerSpawnPoints));
    save->g_animWallCnt = g_animWallCnt;
    Bmemcpy(&save->SpriteDeletionQueue[0],&SpriteDeletionQueue[0],sizeof(SpriteDeletionQueue));
    save->g_spriteDeleteQueuePos = g_spriteDeleteQueuePos;
    Bmemcpy(&save->animwall[0],&animwall[0],sizeof(animwall));
    Bmemcpy(&save->origins[0],&g_origins[0],sizeof(g_origins));
    Bmemcpy(&save->g_mirrorWall[0],&g_mirrorWall[0],sizeof(g_mirrorWall));
    Bmemcpy(&save->g_mirrorSector[0],&g_mirrorSector[0],sizeof(g_mirrorSector));
    save->g_mirrorCount = g_mirrorCount;
    Bmemcpy(&save->show2dsector[0],&show2dsector[0],sizeof(show2dsector));
    save->g_cloudCnt = g_cloudCnt;
    Bmemcpy(&save->g_cloudSect[0],&g_cloudSect[0],sizeof(g_cloudSect));
    save->g_cloudX = g_cloudX;
    save->g_cloudY = g_cloudY;
    save->pskyidx = g_pskyidx;
    Bmemcpy(&save->g_animateGoal[0],&g_animateGoal[0],sizeof(g_animateGoal));
    Bmemcpy(&save->g_animateVel[0],&g_animateVel[0],sizeof(g_animateVel));
    save->g_animateCnt = g_animateCnt;
    Bmemcpy(&save->g_animateSect[0],&g_animateSect[0],sizeof(g_animateSect));

    G_Util_PtrToIdx(g_animatePtr, g_animateCnt, sector, P2I_FWD);
    Bmemcpy(&save->g_animatePtr[0],&g_animatePtr[0],sizeof(g_animatePtr));
    G_Util_PtrToIdx(g_animatePtr, g_animateCnt, sector, P2I_BACK);

    {
        EDUKE32_STATIC_ASSERT(sizeof(save->g_animatePtr) == sizeof(g_animatePtr));
    }

    save->g_playerSpawnCnt = g_playerSpawnCnt;
    save->g_earthquakeTime = g_earthquakeTime;
    save->randomseed       = randomseed;
    save->g_globalRandom   = g_globalRandom;

    for (native_t i=g_gameVarCount-1; i>=0; i--)
    {
        if (aGameVars[i].flags & SAVEGAMEMAPSTATEVARSKIPMASK)
            continue;

        if (aGameVars[i].flags & GAMEVAR_PERPLAYER)
        {
            if (!save->vars[i])
                save->vars[i] = (intptr_t *)Xaligned_alloc(PLAYER_VAR_ALIGNMENT, MAXPLAYERS * sizeof(intptr_t));
            Bmemcpy(&save->vars[i][0], aGameVars[i].pValues, sizeof(intptr_t) * MAXPLAYERS);
        }
        else if (aGameVars[i].flags & GAMEVAR_PERACTOR)
        {
            if (!save->vars[i])
                save->vars[i] = (intptr_t *)Xaligned_alloc(ACTOR_VAR_ALIGNMENT, MAXSPRITES * sizeof(intptr_t));
            Bmemcpy(&save->vars[i][0], aGameVars[i].pValues, sizeof(intptr_t) * MAXSPRITES);
        }
        else
            save->vars[i] = (intptr_t *)aGameVars[i].global;
    }

    for (native_t i=g_gameArrayCount-1; i>=0; i--)
    {
        auto &array = aGameArrays[i];

        if (((array.flags & GAMEARRAY_RESTORE) != GAMEARRAY_RESTORE) || array.flags & SAVEGAMEARRAYSKIPMASK)
            continue;

        save->arraysiz[i] = array.size;
        Xaligned_free(save->arrays[i]);
        save->arrays[i] = (intptr_t *)Xaligned_alloc(ARRAY_ALIGNMENT, Gv_GetArrayAllocSize(i));
        Bmemcpy(&save->arrays[i][0], array.pValues, Gv_GetArrayAllocSize(i));
    }
    ototalclock = totalclock;
}

void G_RestoreMapState(void)
{
    int const   levelNum    = ud.volume_number * MAXLEVELS + ud.level_number;
    mapstate_t *pSavedState = g_mapInfo[levelNum].savedstate;

    if (pSavedState != NULL)
    {
        int playerHealth[MAXPLAYERS];

        for (native_t i=0; i<g_mostConcurrentPlayers; i++)
            playerHealth[i] = sprite[g_player[i].ps->i].extra;

        pub = NUMPAGES;
        pus = NUMPAGES;
        G_UpdateScreenArea();

        numwalls = pSavedState->numwalls;
        Bmemcpy(&wall[0],&pSavedState->wall[0],sizeof(walltype)*MAXWALLS);
#ifndef NEW_MAP_FORMAT
        Bmemcpy(&wallext[0],&pSavedState->wallext[0],sizeof(wallext_t)*MAXWALLS);
#endif
        numsectors = pSavedState->numsectors;
        Bmemcpy(&sector[0],&pSavedState->sector[0],sizeof(sectortype)*MAXSECTORS);
        Bmemcpy(&sprite[0],&pSavedState->sprite[0],sizeof(spritetype)*MAXSPRITES);
        Bmemcpy(&spriteext[0],&pSavedState->spriteext[0],sizeof(spriteext_t)*MAXSPRITES);

        // If we're restoring from EVENT_ANIMATESPRITES, all spriteext[].tspr
        // will be overwritten, so NULL them.
        if (EDUKE32_PREDICT_FALSE(g_currentEvent == EVENT_ANIMATESPRITES))
        {
            LOG_F(ERROR, "CON_LOADMAPSTATE called from EVENT_ANIMATESPRITES. WHY?");
            for (native_t i=0; i<MAXSPRITES; i++)
                spriteext[i].tspr = NULL;
        }
        Numsprites = pSavedState->numsprites;
        tailspritefree = pSavedState->tailspritefree;
        Bmemcpy(&headspritesect[0],&pSavedState->headspritesect[0],sizeof(headspritesect));
        Bmemcpy(&prevspritesect[0],&pSavedState->prevspritesect[0],sizeof(prevspritesect));
        Bmemcpy(&nextspritesect[0],&pSavedState->nextspritesect[0],sizeof(nextspritesect));
        Bmemcpy(&headspritestat[0],&pSavedState->headspritestat[0],sizeof(headspritestat));
        Bmemcpy(&prevspritestat[0],&pSavedState->prevspritestat[0],sizeof(prevspritestat));
        Bmemcpy(&nextspritestat[0],&pSavedState->nextspritestat[0],sizeof(nextspritestat));
#ifdef YAX_ENABLE
        numyaxbunches = pSavedState->numyaxbunches;
# if !defined NEW_MAP_FORMAT
        Bmemcpy(yax_bunchnum, pSavedState->yax_bunchnum, sizeof(yax_bunchnum));
        Bmemcpy(yax_nextwall, pSavedState->yax_nextwall, sizeof(yax_nextwall));
# endif
#endif
        Bmemcpy(&actor[0],&pSavedState->actor[0],sizeof(actor_t)*MAXSPRITES);

        g_cyclerCnt = pSavedState->g_cyclerCnt;
        Bmemcpy(g_cyclers, pSavedState->g_cyclers, sizeof(g_cyclers));
        Bmemcpy(&g_playerSpawnPoints[0],&pSavedState->g_playerSpawnPoints[0],sizeof(g_playerSpawnPoints));
        g_animWallCnt = pSavedState->g_animWallCnt;
        Bmemcpy(&SpriteDeletionQueue[0],&pSavedState->SpriteDeletionQueue[0],sizeof(SpriteDeletionQueue));
        g_spriteDeleteQueuePos = pSavedState->g_spriteDeleteQueuePos;
        Bmemcpy(&animwall[0],&pSavedState->animwall[0],sizeof(animwall));
        Bmemcpy(&g_origins[0],&pSavedState->origins[0],sizeof(g_origins));
        Bmemcpy(&g_mirrorWall[0],&pSavedState->g_mirrorWall[0],sizeof(g_mirrorWall));
        Bmemcpy(&g_mirrorSector[0],&pSavedState->g_mirrorSector[0],sizeof(g_mirrorSector));
        g_mirrorCount = pSavedState->g_mirrorCount;
        Bmemcpy(&show2dsector[0],&pSavedState->show2dsector[0],sizeof(show2dsector));
        g_cloudCnt = pSavedState->g_cloudCnt;
        Bmemcpy(&g_cloudSect[0],&pSavedState->g_cloudSect[0],sizeof(g_cloudSect));
        g_cloudX = pSavedState->g_cloudX;
        g_cloudY = pSavedState->g_cloudY;
        g_pskyidx = pSavedState->pskyidx;
        Bmemcpy(&g_animateGoal[0],&pSavedState->g_animateGoal[0],sizeof(g_animateGoal));
        Bmemcpy(&g_animateVel[0],&pSavedState->g_animateVel[0],sizeof(g_animateVel));
        g_animateCnt = pSavedState->g_animateCnt;
        Bmemcpy(&g_animateSect[0],&pSavedState->g_animateSect[0],sizeof(g_animateSect));

        Bmemcpy(&g_animatePtr[0],&pSavedState->g_animatePtr[0],sizeof(g_animatePtr));
        G_Util_PtrToIdx(g_animatePtr, g_animateCnt, sector, P2I_BACK);

        g_playerSpawnCnt = pSavedState->g_playerSpawnCnt;
        g_earthquakeTime = pSavedState->g_earthquakeTime;
        randomseed = pSavedState->randomseed;
        g_globalRandom = pSavedState->g_globalRandom;

        for (native_t i=g_gameVarCount-1; i>=0; i--)
        {
            if (aGameVars[i].flags & SAVEGAMEMAPSTATEVARSKIPMASK)
                continue;

            if (aGameVars[i].flags & GAMEVAR_PERPLAYER)
            {
                if (!pSavedState->vars[i])
                    continue;
                Bmemcpy(aGameVars[i].pValues, pSavedState->vars[i], sizeof(intptr_t) * MAXPLAYERS);
            }
            else if (aGameVars[i].flags & GAMEVAR_PERACTOR)
            {
                if (!pSavedState->vars[i])
                    continue;
                Bmemcpy(aGameVars[i].pValues, pSavedState->vars[i], sizeof(intptr_t) * MAXSPRITES);
            }
            else
                aGameVars[i].global = (intptr_t)pSavedState->vars[i];
        }

        for (native_t i=g_gameArrayCount-1; i>=0; i--)
        {
            auto &array = aGameArrays[i];

            if (((array.flags & GAMEARRAY_RESTORE) != GAMEARRAY_RESTORE) || array.flags & SAVEGAMEARRAYSKIPMASK)
                continue;

            array.size = pSavedState->arraysiz[i];
            Xaligned_free(array.pValues);
            array.pValues = (intptr_t *) Xaligned_alloc(ARRAY_ALIGNMENT, Gv_GetArrayAllocSize(i));

            Bmemcpy(array.pValues, pSavedState->arrays[i], Gv_GetArrayAllocSize(i));
        }

        Gv_RefreshPointers();
        // Update g_player[].ps->i (sprite indices of players) to be consistent
        // with just loaded sprites.
        // Otherwise, crashes may ensue: e.g. WGR2 SVN r391, map spiderden:
        // - walk forward (to door leading to other level "Shadowpine Forest")
        // - in new level, walk backward to get back to the Spider Den
        // - walk backward to the door leading to Shadowpine Forest --> crash.
        for (native_t SPRITES_OF(STAT_PLAYER, i))
        {
            int32_t snum = P_Get(i);
            Bassert((unsigned)snum < MAXPLAYERS);
            g_player[snum].ps->i = i;
        }

        for (native_t i=0; i<g_mostConcurrentPlayers; i++)
            sprite[g_player[i].ps->i].extra = playerHealth[i];

        if (g_player[myconnectindex].ps->over_shoulder_on != 0)
        {
            CAMERADIST = 0;
            CAMERACLOCK = 0;
            g_player[myconnectindex].ps->over_shoulder_on = 1;
        }

        screenpeek = myconnectindex;

#ifndef EDUKE32_STANDALONE
        if (ud.lockout)
        {
            for (native_t x=g_animWallCnt-1; x>=0; x--)
                switch (tileGetMapping(wall[animwall[x].wallnum].picnum))
                {
                    case FEMPIC1__: wall[animwall[x].wallnum].picnum = BLANKSCREEN; break;
                    case FEMPIC2__:
                    case FEMPIC3__: wall[animwall[x].wallnum].picnum = SCREENBREAK6; break;
                }
        }
#if 0
        else
        {
            for (native_t x=g_numAnimWalls-1; x>=0; x--)
                if (wall[animwall[x].wallnum].extra >= 0)
                    wall[animwall[x].wallnum].picnum = wall[animwall[x].wallnum].extra;
        }
#endif
#endif
#ifdef YAX_ENABLE
        sv_postyaxload();
#endif
        calc_sector_reachability();
        G_ResetInterpolations();

        Net_ResetPrediction();

        G_ClearFIFO();
        G_ResetTimers(0);
    }
}

// MYOS* CON commands.
void VM_DrawTileGeneric(int32_t x, int32_t y, int32_t zoom, int32_t tilenum, int32_t shade, int32_t orientation, int32_t p)
{
    orientation &= (ROTATESPRITE_MAX-1);

    int const rotAngle = (orientation&4) ? 1024 : 0;

    if (!(orientation&ROTATESPRITE_FULL16))
    {
        x<<=16;
        y<<=16;
    }

    rotatesprite_win(x, y, zoom, rotAngle, tilenum, shade, p, 2|orientation);
}

void VM_DrawTile(int32_t x, int32_t y, int32_t tilenum, int32_t shade, int32_t orientation)
{
    auto const pPlayer = g_player[screenpeek].ps;
    int32_t    tilePal = pPlayer->cursectnum >= 0 ? sector[pPlayer->cursectnum].floorpal : 0;

    VM_DrawTileGeneric(x, y, 65536, tilenum, shade, orientation, tilePal);
}

void VM_DrawTileSmall(int32_t x, int32_t y, int32_t tilenum, int32_t shade, int32_t orientation)
{
    auto const pPlayer = g_player[screenpeek].ps;
    int32_t    tilePal = pPlayer->cursectnum >= 0 ? sector[pPlayer->cursectnum].floorpal : 0;

    VM_DrawTileGeneric(x, y, 32768, tilenum, shade, orientation, tilePal);
}