// xenia-rs Xenos runtime microcode interpreter — P3b WGSL.
//
// Bindings (stable across P3b milestones):
//   @group(0) @binding(0) draw_ctx      (uniform, 16 B — XenosDrawConstants)
//   @group(0) @binding(1) xenos_consts  (uniform, ~9.2 KB — XenosConstants)
//   @group(0) @binding(2) vs_ucode      (storage<read>, packed VS shader)
//   @group(0) @binding(3) ps_ucode      (storage<read>, packed PS shader)
//   @group(0) @binding(4) vertex_buffer (storage<read>, raw guest VB dwords)
//
// Packed shader layout (both vs_ucode & ps_ucode):
//   [0]                            = cf_count
//   [1 .. 1 + cf_count*3]          = CF table: (kind, primary, aux) × cf_count
//   [1 + cf_count*3 ..]            = instruction triples (3 dwords each)
//
// M3 state (this file): CF walker + operand decode helpers + register file
// scaffold are complete. ALU / fetch bodies are still stubs that fall back
// to the procedural-circle visualisation; M4-M7 fill them in.

struct XenosDrawConstants {
    draw_index: u32,
    vertex_count: u32,
    prim_kind: u32,
    // iterate-3O: guest dword address that maps to index 0 of `vertex_buffer`.
    // The CPU uploads a bounded guest-memory window starting at the active
    // vertex-fetch base; the shader subtracts this base from the absolute
    // fetch-constant address so it indexes the uploaded window. 0 means "no
    // real vertex window" (procedural fallback path).
    vertex_base_dwords: u32,
    // iterate-3S: guest viewport → host NDC XY transform (Y pre-flipped).
    ndc_scale: vec2<f32>,
    ndc_offset: vec2<f32>,
};

struct XenosConstants {
    alu:          array<vec4<f32>, 512>,
    fetch:        array<u32, 256>,
    bool_consts:  array<u32, 8>,
    loop_consts:  array<u32, 32>,
};

@group(0) @binding(0) var<uniform>            draw_ctx      : XenosDrawConstants;
// `xenos_consts` is a read-only storage buffer (not uniform) because the
// block contains tightly-packed `array<u32, N>` fields — WGSL's uniform
// address space requires 16-byte element stride, which would triple the
// allocation; storage accepts the natural 4-byte stride.
@group(0) @binding(1) var<storage, read>      xenos_consts  : XenosConstants;
@group(0) @binding(2) var<storage, read>      vs_ucode      : array<u32>;
@group(0) @binding(3) var<storage, read>      ps_ucode      : array<u32>;
@group(0) @binding(4) var<storage, read>      vertex_buffer : array<u32>;

// Multi-texture: group(1) exposes up to 8 sampled texture slots (binding 0 =
// shared sampler, bindings 1..8 = tex0..tex7). A pixel shader that samples
// several fetch constants at once — e.g. the intro video's YUV420 planes
// (Y, U, V) — reads the right texture per `tfetch` slot. Slots the draw does
// not bind carry a transparent 1×1 dummy. `interpret_texture_fetch` selects
// the slot from the fetch instruction's fetch-constant index.
@group(1) @binding(0) var xenos_samp : sampler;
@group(1) @binding(1) var xenos_tex0 : texture_2d<f32>;
@group(1) @binding(2) var xenos_tex1 : texture_2d<f32>;
@group(1) @binding(3) var xenos_tex2 : texture_2d<f32>;
@group(1) @binding(4) var xenos_tex3 : texture_2d<f32>;
@group(1) @binding(5) var xenos_tex4 : texture_2d<f32>;
@group(1) @binding(6) var xenos_tex5 : texture_2d<f32>;
@group(1) @binding(7) var xenos_tex6 : texture_2d<f32>;
@group(1) @binding(8) var xenos_tex7 : texture_2d<f32>;

// ── CF kind codes; must match `xenia_gpu::ucode::cf_kind`. ─────────────
const CF_KIND_EXEC:        u32 = 0u;
const CF_KIND_EXEC_END:    u32 = 1u;
const CF_KIND_ALLOC:       u32 = 2u;
const CF_KIND_EXIT:        u32 = 3u;
const CF_KIND_LOOP_START:  u32 = 4u;
const CF_KIND_LOOP_END:    u32 = 5u;
const CF_KIND_COND_JMP:    u32 = 6u;
const CF_KIND_COND_CALL:   u32 = 7u;
const CF_KIND_RETURN:      u32 = 8u;
const CF_KIND_NOP:         u32 = 9u;
const CF_KIND_UNKNOWN:     u32 = 15u;

// ── Alloc-kind codes (mirrors `xenia_gpu::ucode::cf_alloc_kind`). ──────
const ALLOC_KIND_POSITION:      u32 = 0u;
const ALLOC_KIND_INTERPOLATORS: u32 = 1u;
const ALLOC_KIND_COLORS:        u32 = 2u;

// Per-invocation Xenos register file + scalar `ps` + predicate.
var<private> registers: array<vec4<f32>, 128>;
var<private> ps: f32;
var<private> predicate: bool;

// Currently-active export alloc kind; set by Alloc clauses.
var<private> current_alloc: u32;

// P3c additions:
//   `kill_flag` — set by vector/scalar kill ops; PS calls `discard` after the
//                 interpreter exits. (`discard` inside a helper function is
//                 allowed in WGSL, but keeping it at the entry level makes
//                 control flow easier to read.)
//   `loop_depth`/`loop_counters` — bookkeeping for LoopStart/LoopEnd CF
//                 clauses. Xenos supports up to 4 nested loops.
//   `reject_mask` — bitfield of op categories we failed to interpret, so the
//                 PS fallback color + host-side diagnostics can surface it.
var<private> kill_flag: bool;
var<private> loop_depth: u32;
var<private> loop_counters: array<u32, 4>;
var<private> loop_starts: array<u32, 4>;
var<private> reject_mask: u32;

const REJECT_ALU_VEC:      u32 = 1u;
const REJECT_ALU_SCL:      u32 = 2u;
const REJECT_TEX_NON2D:    u32 = 4u;
const REJECT_VFETCH_FMT:   u32 = 8u;
const REJECT_CF_JUMP:      u32 = 16u;
const REJECT_CF_CALL:      u32 = 32u;
const REJECT_LOOP_OVERFLOW:u32 = 64u;

struct VsOut {
    @builtin(position) position: vec4<f32>,
    @location(0) color: vec4<f32>,
};

struct FsOut {
    @location(0) color0: vec4<f32>,
};

// ── CF helpers: read (kind,primary,aux) from a packed ucode storage buffer.

fn vs_cf_count() -> u32 { return vs_ucode[0]; }
fn ps_cf_count() -> u32 { return ps_ucode[0]; }

fn vs_cf_kind(i: u32) -> u32    { return vs_ucode[1u + i * 3u + 0u] & 0xFFu; }
fn vs_cf_primary(i: u32) -> u32 { return vs_ucode[1u + i * 3u + 1u]; }
fn vs_cf_aux(i: u32) -> u32     { return vs_ucode[1u + i * 3u + 2u]; }
fn ps_cf_kind(i: u32) -> u32    { return ps_ucode[1u + i * 3u + 0u] & 0xFFu; }
fn ps_cf_primary(i: u32) -> u32 { return ps_ucode[1u + i * 3u + 1u]; }
fn ps_cf_aux(i: u32) -> u32     { return ps_ucode[1u + i * 3u + 2u]; }

fn vs_instr_base() -> u32 { return 1u + vs_cf_count() * 3u; }
fn ps_instr_base() -> u32 { return 1u + ps_cf_count() * 3u; }

// Fetch one of the 3 dwords of an ALU/fetch triple at triple index `t`.
fn vs_instr_dword(t: u32, which: u32) -> u32 {
    return vs_ucode[vs_instr_base() + t * 3u + which];
}
fn ps_instr_dword(t: u32, which: u32) -> u32 {
    return ps_ucode[ps_instr_base() + t * 3u + which];
}

// ── Operand helpers: M3 scaffold; M4 wires them into interpret_alu.

fn apply_swizzle(v: vec4<f32>, swizzle: u32) -> vec4<f32> {
    // Per canary `xenia-canary/src/xenia/gpu/ucode.h:1996`
    // (`AluInstruction::GetSwizzledComponentIndex`): the 8-bit swizzle is
    // **component-relative**, not absolute. For output component i, the
    // selected source component is `((swizzle >> (2*i)) + i) & 3`. Identity
    // swizzle is therefore 0x00 (all bits zero), NOT 0xE4. GPUBUG-100.
    let sx = ((swizzle >> 0u) + 0u) & 3u;
    let sy = ((swizzle >> 2u) + 1u) & 3u;
    let sz = ((swizzle >> 4u) + 2u) & 3u;
    let sw = ((swizzle >> 6u) + 3u) & 3u;
    return vec4<f32>(v[sx], v[sy], v[sz], v[sw]);
}

fn apply_modifiers(v: vec4<f32>, negate: bool, take_abs: bool) -> vec4<f32> {
    var r = v;
    if take_abs {
        r = abs(r);
    }
    if negate {
        r = -r;
    }
    return r;
}

// GPUBUG-102: per-format byte-swap matching canary `GpuSwapInline`
// (xenos.h:1090-1109). Xbox 360 vertex data is big-endian; host is
// little-endian. The fetch constant's `endian` field (low 2 bits of
// dword_1) selects the swap pattern.
fn gpu_swap(value: u32, endian: u32) -> u32 {
    switch endian {
        case 1u: { return ((value << 8u) & 0xFF00FF00u) | ((value >> 8u) & 0x00FF00FFu); }
        case 2u: {
            return ((value & 0x000000FFu) << 24u)
                 | ((value & 0x0000FF00u) << 8u)
                 | ((value & 0x00FF0000u) >> 8u)
                 | ((value & 0xFF000000u) >> 24u);
        }
        case 3u: { return ((value >> 16u) & 0xFFFFu) | (value << 16u); }
        default: { return value; }
    }
}

fn write_reg_masked(idx: u32, mask: u32, value: vec4<f32>) {
    if idx >= 128u {
        return;
    }
    let prev = registers[idx];
    var out = prev;
    if (mask & 1u) != 0u { out.x = value.x; }
    if (mask & 2u) != 0u { out.y = value.y; }
    if (mask & 4u) != 0u { out.z = value.z; }
    if (mask & 8u) != 0u { out.w = value.w; }
    registers[idx] = out;
}

// ── Xenos ALU opcodes. Values match canary's `AluVectorOpcode` and
// `AluScalarOpcode` enums in `ucode.h:1001,1354` (also mirrored in
// `xenia_gpu::ucode::alu::{vop,sop}`).
const VOP_ADD:          u32 = 0u;
const VOP_MUL:          u32 = 1u;
const VOP_MAX:          u32 = 2u;
const VOP_MIN:          u32 = 3u;
const VOP_SEQ:          u32 = 4u;
const VOP_SGT:          u32 = 5u;
const VOP_SGE:          u32 = 6u;
const VOP_SNE:          u32 = 7u;
const VOP_FRC:          u32 = 8u;
const VOP_TRUNC:        u32 = 9u;
const VOP_FLOOR:        u32 = 10u;
const VOP_MAD:          u32 = 11u;
const VOP_CND_EQ:       u32 = 12u;
const VOP_CND_GE:       u32 = 13u;
const VOP_CND_GT:       u32 = 14u;
const VOP_DP4:          u32 = 15u;
const VOP_DP3:          u32 = 16u;
const VOP_DP2_ADD:      u32 = 17u;
const VOP_CUBE:         u32 = 18u;
const VOP_MAX4:         u32 = 19u;
const VOP_KILL_EQ:      u32 = 24u;
const VOP_KILL_GT:      u32 = 25u;
const VOP_KILL_GE:      u32 = 26u;
const VOP_KILL_NE:      u32 = 27u;
const VOP_DST:          u32 = 28u;
const VOP_MAX_A:        u32 = 29u;

const SOP_ADDS:         u32 = 0u;
const SOP_ADDS_PREV:    u32 = 1u;
const SOP_MULS:         u32 = 2u;
const SOP_MULS_PREV:    u32 = 3u;
const SOP_MAXS:         u32 = 5u;
const SOP_MINS:         u32 = 6u;
const SOP_SEQS:         u32 = 7u;
const SOP_SGTS:         u32 = 8u;
const SOP_SGES:         u32 = 9u;
const SOP_SNES:         u32 = 10u;
const SOP_FRCS:         u32 = 11u;
const SOP_TRUNCS:       u32 = 12u;
const SOP_FLOORS:       u32 = 13u;
const SOP_EXP:          u32 = 14u;
const SOP_LOGC:         u32 = 15u;
const SOP_LOG:          u32 = 16u;
const SOP_RCPC:         u32 = 17u;
const SOP_RCPF:         u32 = 18u;
const SOP_RCP:          u32 = 19u;
const SOP_RSQC:         u32 = 20u;
const SOP_RSQF:         u32 = 21u;
const SOP_RSQ:          u32 = 22u;
const SOP_SUBS:         u32 = 25u;
const SOP_SUBS_PREV:    u32 = 26u;
const SOP_SETP_EQ:      u32 = 27u;
const SOP_SETP_NE:      u32 = 28u;
const SOP_SETP_GT:      u32 = 29u;
const SOP_SETP_GE:      u32 = 30u;
const SOP_SETP_INV:     u32 = 31u;
const SOP_SETP_POP:     u32 = 32u;
const SOP_SETP_CLR:     u32 = 33u;
const SOP_SETP_RSTR:    u32 = 34u;
const SOP_KILLS_EQ:     u32 = 35u;
const SOP_KILLS_GT:     u32 = 36u;
const SOP_KILLS_GE:     u32 = 37u;
const SOP_KILLS_NE:     u32 = 38u;
const SOP_KILLS_ONE:    u32 = 39u;
const SOP_SQRT:         u32 = 40u;
const SOP_MULSC0:       u32 = 42u;
const SOP_MULSC1:       u32 = 43u;
const SOP_ADDSC0:       u32 = 44u;
const SOP_ADDSC1:       u32 = 45u;
const SOP_SUBSC0:       u32 = 46u;
const SOP_SUBSC1:       u32 = 47u;
const SOP_SIN:          u32 = 48u;
const SOP_COS:          u32 = 49u;
const SOP_RETAIN_PREV:  u32 = 50u;

// Read a vec4 source. Per canary `xenia-canary/src/xenia/gpu/ucode.h`
// the temp-vs-constant selector lives in word-0 bits 29-31
// (`srcN_sel`); the corresponding 8-bit src_byte is **either** a temp
// register descriptor (bit 7 = abs flag, bit 6 = relative, bits 5:0 =
// temp index) **or** a flat constant index (full byte). Pre-fix, the
// MVP masked `& 0x7F` and read `registers[low7]` regardless — every
// shader's WVP matrix / light constant / per-frame uniform read came
// back as r[low7] (typically zero → invisible rendering). GPUBUG-101.
fn read_src(src_byte: u32, is_temp: bool) -> vec4<f32> {
    if is_temp {
        // Bits 5:0 of the byte give the temp index; bit 7 (abs) and
        // bit 6 (relative) are handled in read_src_full when modifiers
        // land in Phase D2.
        return registers[src_byte & 0x3Fu];
    }
    // Constant index — full byte (covers c0..c127 in bank 0; higher
    // banks via cf-level relative addressing land in a later phase).
    return xenos_consts.alu[src_byte & 0xFFu];
}

fn exec_vector_op(op: u32, a: vec4<f32>, b: vec4<f32>, c: vec4<f32>) -> vec4<f32> {
    switch op {
        case VOP_ADD:     { return a + b; }
        case VOP_MUL:     { return a * b; }
        case VOP_MAX:     { return max(a, b); }
        case VOP_MIN:     { return min(a, b); }
        case VOP_SEQ:     {
            return vec4<f32>(
                select(0.0, 1.0, a.x == b.x),
                select(0.0, 1.0, a.y == b.y),
                select(0.0, 1.0, a.z == b.z),
                select(0.0, 1.0, a.w == b.w),
            );
        }
        case VOP_SGT:     {
            return vec4<f32>(
                select(0.0, 1.0, a.x >  b.x),
                select(0.0, 1.0, a.y >  b.y),
                select(0.0, 1.0, a.z >  b.z),
                select(0.0, 1.0, a.w >  b.w),
            );
        }
        case VOP_SGE:     {
            return vec4<f32>(
                select(0.0, 1.0, a.x >= b.x),
                select(0.0, 1.0, a.y >= b.y),
                select(0.0, 1.0, a.z >= b.z),
                select(0.0, 1.0, a.w >= b.w),
            );
        }
        case VOP_SNE:     {
            return vec4<f32>(
                select(0.0, 1.0, a.x != b.x),
                select(0.0, 1.0, a.y != b.y),
                select(0.0, 1.0, a.z != b.z),
                select(0.0, 1.0, a.w != b.w),
            );
        }
        case VOP_FRC:     { return fract(a); }
        case VOP_TRUNC:   { return trunc(a); }
        case VOP_FLOOR:   { return floor(a); }
        case VOP_MAD:     { return a * b + c; }
        case VOP_CND_EQ: {
            // dst = (src0 == 0) ? src1 : src2
            return vec4<f32>(
                select(c.x, b.x, a.x == 0.0),
                select(c.y, b.y, a.y == 0.0),
                select(c.z, b.z, a.z == 0.0),
                select(c.w, b.w, a.w == 0.0),
            );
        }
        case VOP_CND_GE: {
            return vec4<f32>(
                select(c.x, b.x, a.x >= 0.0),
                select(c.y, b.y, a.y >= 0.0),
                select(c.z, b.z, a.z >= 0.0),
                select(c.w, b.w, a.w >= 0.0),
            );
        }
        case VOP_CND_GT: {
            return vec4<f32>(
                select(c.x, b.x, a.x > 0.0),
                select(c.y, b.y, a.y > 0.0),
                select(c.z, b.z, a.z > 0.0),
                select(c.w, b.w, a.w > 0.0),
            );
        }
        case VOP_DP4:     {
            let d = dot(a, b);
            return vec4<f32>(d, d, d, d);
        }
        case VOP_DP3:     {
            let d = dot(a.xyz, b.xyz);
            return vec4<f32>(d, d, d, d);
        }
        case VOP_DP2_ADD: {
            let d = a.x * b.x + a.y * b.y + c.x;
            return vec4<f32>(d, d, d, d);
        }
        case VOP_MAX4: {
            let m = max(max(a.x, a.y), max(a.z, a.w));
            return vec4<f32>(m, m, m, m);
        }
        case VOP_KILL_EQ: {
            if a.x == b.x || a.y == b.y || a.z == b.z || a.w == b.w {
                kill_flag = true;
                return vec4<f32>(1.0, 1.0, 1.0, 1.0);
            }
            return vec4<f32>(0.0, 0.0, 0.0, 0.0);
        }
        case VOP_KILL_GT: {
            if a.x > b.x || a.y > b.y || a.z > b.z || a.w > b.w {
                kill_flag = true;
                return vec4<f32>(1.0, 1.0, 1.0, 1.0);
            }
            return vec4<f32>(0.0, 0.0, 0.0, 0.0);
        }
        case VOP_KILL_GE: {
            if a.x >= b.x || a.y >= b.y || a.z >= b.z || a.w >= b.w {
                kill_flag = true;
                return vec4<f32>(1.0, 1.0, 1.0, 1.0);
            }
            return vec4<f32>(0.0, 0.0, 0.0, 0.0);
        }
        case VOP_KILL_NE: {
            if a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w {
                kill_flag = true;
                return vec4<f32>(1.0, 1.0, 1.0, 1.0);
            }
            return vec4<f32>(0.0, 0.0, 0.0, 0.0);
        }
        case VOP_DST: {
            // dest = (1, src0.y * src1.y, src0.z, src1.w)
            return vec4<f32>(1.0, a.y * b.y, a.z, b.w);
        }
        case VOP_CUBE, VOP_MAX_A: {
            // Cube face projection + MAX+AR are rare in P3c's target set;
            // forward to max() fallback so MAX_A degrades gracefully and
            // CUBE does *something* useful (max-axis selection).
            reject_mask |= REJECT_ALU_VEC;
            return max(a, b);
        }
        default: {
            // Unsupported — identity fallback + diagnostic flag.
            reject_mask |= REJECT_ALU_VEC;
            return vec4<f32>(0.0, 0.0, 0.0, 0.0);
        }
    }
}

// Scalar op executor. Takes:
//   `src_a` / `src_b` — the two scalar source components (most ops use
//   only `src_a`; binary ops — ADDS/MULS/MAXS/MINS/SUBS — use both).
//   `prev` — current `ps` chain value.
// Kill/setp ops with side-effects also touch `kill_flag`/`predicate`.
fn exec_scalar_op(op: u32, src_a: f32, src_b: f32, prev: f32) -> f32 {
    switch op {
        case SOP_ADDS:        { return src_a + src_b; }
        case SOP_ADDS_PREV:   { return src_a + prev; }
        case SOP_MULS:        { return src_a * src_b; }
        case SOP_MULS_PREV:   { return src_a * prev; }
        case SOP_SUBS:        { return src_a - src_b; }
        case SOP_SUBS_PREV:   { return src_a - prev; }
        case SOP_MAXS:        { return max(src_a, src_b); }
        case SOP_MINS:        { return min(src_a, src_b); }
        case SOP_SEQS:        { return select(0.0, 1.0, src_a == 0.0); }
        case SOP_SGTS:        { return select(0.0, 1.0, src_a >  0.0); }
        case SOP_SGES:        { return select(0.0, 1.0, src_a >= 0.0); }
        case SOP_SNES:        { return select(0.0, 1.0, src_a != 0.0); }
        case SOP_FRCS:        { return fract(src_a); }
        case SOP_TRUNCS:      { return trunc(src_a); }
        case SOP_FLOORS:      { return floor(src_a); }
        case SOP_EXP:         {
            // exp/EXP_IEEE: pow(2, src). Canary says src==0 -> 1.0, which
            // WGSL's exp2 already produces for 0.
            return exp2(src_a);
        }
        case SOP_LOG, SOP_LOGC: {
            // log/logc both compute log2. Canary's LOGC clamps -INF to
            // -FLT_MAX, but WGSL's log2 doesn't produce -INF for finite
            // inputs > 0, and we leave src <= 0 to be the shader author's
            // problem (identity fallback via select).
            return select(log2(src_a), 0.0, src_a == 1.0);
        }
        case SOP_RCP, SOP_RCPC, SOP_RCPF: {
            // IEEE reciprocal with src==0 guard; the clamp-variants differ
            // only in how they treat INF/NaN, which is shader-author-rare.
            return select(0.0, 1.0 / src_a, src_a != 0.0);
        }
        case SOP_RSQ, SOP_RSQC, SOP_RSQF: {
            return select(0.0, inverseSqrt(src_a), src_a > 0.0);
        }
        case SOP_SQRT:        { return select(0.0, sqrt(src_a), src_a >= 0.0); }
        case SOP_SIN:         { return sin(src_a); }
        case SOP_COS:         { return cos(src_a); }
        // Scalar-constant family (canary kMulsc0..kSubsc1): ps = src0.x OP
        // src1.x. src_a/src_b already carry src0.x/src1.x, so c0/c1 collapse to
        // the same arithmetic. The intro video's YUV→RGB shader uses these.
        case SOP_MULSC0, SOP_MULSC1: { return src_a * src_b; }
        case SOP_ADDSC0, SOP_ADDSC1: { return src_a + src_b; }
        case SOP_SUBSC0, SOP_SUBSC1: { return src_a - src_b; }
        // Predicate writes — update `predicate` and produce a result that
        // the surrounding ALU slot can still consume via `ps`. Canary's
        // setp-variant dst-write semantics are preserved.
        case SOP_SETP_EQ: {
            predicate = (src_a == 0.0);
            return select(1.0, 0.0, src_a == 0.0);
        }
        case SOP_SETP_NE: {
            predicate = (src_a != 0.0);
            return select(1.0, 0.0, src_a != 0.0);
        }
        case SOP_SETP_GT: {
            predicate = (src_a > 0.0);
            return select(1.0, 0.0, src_a > 0.0);
        }
        case SOP_SETP_GE: {
            predicate = (src_a >= 0.0);
            return select(1.0, 0.0, src_a >= 0.0);
        }
        case SOP_SETP_INV: {
            if src_a == 1.0 {
                predicate = true;
                return 0.0;
            } else {
                predicate = false;
                return select(src_a, 1.0, src_a == 0.0);
            }
        }
        case SOP_SETP_POP: {
            if src_a - 1.0 <= 0.0 {
                predicate = true;
                return 0.0;
            } else {
                predicate = false;
                return src_a - 1.0;
            }
        }
        case SOP_SETP_CLR: {
            predicate = false;
            // FLT_MAX sentinel. WGSL's bitcast keeps this portable.
            return bitcast<f32>(0x7F7FFFFFu);
        }
        case SOP_SETP_RSTR: {
            if src_a == 0.0 {
                predicate = true;
                return 0.0;
            } else {
                predicate = false;
                return src_a;
            }
        }
        // Pixel kill — set kill_flag; the dest value is 1.0 / 0.0 per
        // canary, and `discard` runs at fragment exit when the flag is set.
        case SOP_KILLS_EQ: {
            if src_a == 0.0 { kill_flag = true; return 1.0; }
            return 0.0;
        }
        case SOP_KILLS_GT: {
            if src_a > 0.0 { kill_flag = true; return 1.0; }
            return 0.0;
        }
        case SOP_KILLS_GE: {
            if src_a >= 0.0 { kill_flag = true; return 1.0; }
            return 0.0;
        }
        case SOP_KILLS_NE: {
            if src_a != 0.0 { kill_flag = true; return 1.0; }
            return 0.0;
        }
        case SOP_KILLS_ONE: {
            if src_a == 1.0 { kill_flag = true; return 1.0; }
            return 0.0;
        }
        case SOP_RETAIN_PREV: { return prev; }
        default: {
            reject_mask |= REJECT_ALU_SCL;
            return 0.0;
        }
    }
}

fn interpret_alu(t: u32, is_vertex: bool) {
    // Read the 3-dword instruction triple.
    var w0: u32;
    var w1: u32;
    var w2: u32;
    if is_vertex {
        w0 = vs_instr_dword(t, 0u);
        w1 = vs_instr_dword(t, 1u);
        w2 = vs_instr_dword(t, 2u);
    } else {
        w0 = ps_instr_dword(t, 0u);
        w1 = ps_instr_dword(t, 1u);
        w2 = ps_instr_dword(t, 2u);
    }
    // Field extraction matches `xenia_gpu::ucode::alu::decode_alu`.
    let vec_op  = w2 & 0x3Fu;
    let scl_op  = (w2 >> 6u) & 0x3Fu;
    let vec_dst = (w2 >> 16u) & 0x7Fu;
    let scl_dst = (w2 >> 24u) & 0x7Fu;
    let vec_wm  = (w2 >> 12u) & 0xFu;
    let scl_wm  = (w2 >> 8u)  & 0xFu;
    let src_a   = w0 & 0xFFu;
    let src_b   = (w0 >> 8u)  & 0xFFu;
    let src_c   = (w0 >> 16u) & 0xFFu;
    // GPUBUG-101: word-0 bits 29-31 are the per-operand temp-vs-constant
    // selectors (canary `srcN_sel`, ucode.h:2078-2086). `src_a` is
    // canary's third operand (low byte), so its selector is bit 29.
    let src_a_is_temp = ((w0 >> 29u) & 1u) != 0u;
    let src_b_is_temp = ((w0 >> 30u) & 1u) != 0u;
    let src_c_is_temp = ((w0 >> 31u) & 1u) != 0u;
    let predicated           = ((w0 >> 27u) & 1u) != 0u;
    let predicate_condition  = ((w0 >> 28u) & 1u) != 0u;
    let scalar_src_is_ps     = ((w0 >> 26u) & 1u) != 0u;
    // GPUBUG-100: word-1 holds per-operand swizzle (8 bits each, low to
    // high = src3/src2/src1 in canary's numbering) + per-operand negate
    // (bits 24/25/26). Pre-fix `_ = w1;` discarded these → every ALU
    // result was missing its swizzle (e.g. `.zyxw`, `.xxxx` broadcast)
    // and any negated operand was used positive instead.
    let src_a_swiz   =  w1        & 0xFFu;
    let src_b_swiz   = (w1 >> 8u) & 0xFFu;
    let src_c_swiz   = (w1 >> 16u) & 0xFFu;
    let src_a_neg    = ((w1 >> 24u) & 1u) != 0u;
    let src_b_neg    = ((w1 >> 25u) & 1u) != 0u;
    let src_c_neg    = ((w1 >> 26u) & 1u) != 0u;

    // Honor per-instruction predicate: skip when predicated and the
    // predicate doesn't match the required condition.
    if predicated && (predicate != predicate_condition) {
        return;
    }

    // Vector pipe — apply swizzle then negate per operand. Abs is
    // omitted (the abs flag is dual-meaning and lives at different
    // bits depending on temp-vs-constant; left for a follow-up).
    let a_raw = read_src(src_a, src_a_is_temp);
    let b_raw = read_src(src_b, src_b_is_temp);
    let c_raw = read_src(src_c, src_c_is_temp);
    let a = apply_modifiers(apply_swizzle(a_raw, src_a_swiz), src_a_neg, false);
    let b = apply_modifiers(apply_swizzle(b_raw, src_b_swiz), src_b_neg, false);
    let c = apply_modifiers(apply_swizzle(c_raw, src_c_swiz), src_c_neg, false);
    let vec_result = exec_vector_op(vec_op, a, b, c);
    if vec_wm != 0u {
        write_reg_masked(vec_dst, vec_wm, vec_result);
    }

    // Scalar pipe. Binary scalar ops read (src_a.x, src_b.x); ps-variants
    // read src_a.x and chain through the running `ps`. When `scalar_src_is_ps`
    // is set the operand selector chooses `ps` as the primary source.
    let scl_src_a = select(a.x, ps, scalar_src_is_ps);
    let scl_src_b = b.x;
    let new_ps = exec_scalar_op(scl_op, scl_src_a, scl_src_b, ps);
    ps = new_ps;
    if scl_wm != 0u {
        write_reg_masked(scl_dst, scl_wm, vec4<f32>(new_ps, new_ps, new_ps, new_ps));
    }
}
// Xenos VertexFormat values from `xenos.h:641`.
const VFMT_8_8_8_8:         u32 = 6u;
const VFMT_2_10_10_10:      u32 = 7u;
const VFMT_10_11_11:        u32 = 16u;
const VFMT_11_11_10:        u32 = 17u;
const VFMT_16_16:           u32 = 25u;
const VFMT_16_16_16_16:     u32 = 26u;
const VFMT_16_16_FLOAT:     u32 = 31u;
const VFMT_16_16_16_16_FLOAT:u32 = 32u;
const VFMT_32:              u32 = 33u;
const VFMT_32_32:           u32 = 34u;
const VFMT_32_32_32_32:     u32 = 35u;
const VFMT_32_FLOAT:        u32 = 36u;
const VFMT_32_32_FLOAT:     u32 = 37u;
const VFMT_32_32_32_32_FLOAT:u32 = 38u;
const VFMT_32_32_32_FLOAT:  u32 = 57u;

// Decode vertex fetch instruction fields (canary's VertexFetchInstruction
// layout in `ucode.h:690`):
//   w0 [4:0]   opcode
//   w0 [10:5]  src_reg[5:0]
//   w0 [17:12] dst_reg[5:0]
//   w0 [24:20] const_index[4:0], [26:25] const_index_sel[1:0]
//   w1 [21:16] format[5:0]
//   w2 [7:0]   stride (in dwords)
//   w2 [30:8]  offset (signed, in dwords)
//
// Pragmatic subset: we read format/stride; offset, exp_adjust, swizzle,
// sign/normalization flags are used for the most-common normalized-unsigned
// path. Rejects set `REJECT_VFETCH_FMT`.
fn interpret_vertex_fetch(t: u32) {
    let w0 = vs_instr_dword(t, 0u);
    let w1 = vs_instr_dword(t, 1u);
    let w2 = vs_instr_dword(t, 2u);
    let fetch_const = (w0 >> 20u) & 0x1Fu;
    let dst_reg = (w0 >> 12u) & 0x3Fu;
    let src_reg = (w0 >> 5u) & 0x3Fu;
    let format  = (w1 >> 16u) & 0x3Fu;
    let stride  = w2 & 0xFFu;

    // Vertex-fetch constant: dword 0 carries (type[1:0], address[31:2]);
    // dword 1 carries (endian[1:0], size[25:2]).
    let fc0 = xenos_consts.fetch[fetch_const * 2u + 0u];
    let fc1 = xenos_consts.fetch[fetch_const * 2u + 1u];
    // iterate-3O: the fetch constant holds an *absolute* guest dword address.
    // The CPU uploaded a window of guest memory starting at
    // `draw_ctx.vertex_base_dwords`, so rebase the absolute address into that
    // window. When no real window was published (`vertex_base_dwords == 0`)
    // keep the absolute value (the `addr < n` guards below then skip the read
    // and the procedural fallback position is used).
    // GPUBUG-108 (iterate-3S): the captured window begins exactly at the fetch
    // base, so index from 0 (vertex i at i*stride). The uniform `fetch[]` holds
    // the last-published per-frame constant, not this draw's — recomputing
    // `abs_base` from it produced a stale out-of-window address (the splash
    // collapsed to one pixel). Only consult the uniform for the no-window
    // synthetic fallback.
    let abs_base = (fc0 & 0xFFFFFFFCu) >> 2u;
    let base_dwords = select(abs_base, 0u, draw_ctx.vertex_base_dwords != 0u);
    // GPUBUG-102: per-format endian byte-swap. Xbox 360 vertex data is
    // big-endian; the host is little-endian. Pre-fix every dword was
    // bitcast as-is — vertex positions were byte-reversed garbage.
    let endian = fc1 & 0x3u;

    let vidx = u32(registers[src_reg & 0x7Fu].x);
    // Per-vertex byte offset; stride==0 means 1 element total (non-indexed).
    let effective_stride = select(stride, 4u, stride == 0u);
    let addr = base_dwords + vidx * effective_stride;

    let n = arrayLength(&vertex_buffer);
    var result: vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 1.0);

    switch format {
        case VFMT_32_32_32_32_FLOAT: {
            if addr + 3u < n {
                result = vec4<f32>(
                    bitcast<f32>(gpu_swap(vertex_buffer[addr + 0u], endian)),
                    bitcast<f32>(gpu_swap(vertex_buffer[addr + 1u], endian)),
                    bitcast<f32>(gpu_swap(vertex_buffer[addr + 2u], endian)),
                    bitcast<f32>(gpu_swap(vertex_buffer[addr + 3u], endian)),
                );
            }
        }
        case VFMT_32_32_32_FLOAT: {
            if addr + 2u < n {
                result = vec4<f32>(
                    bitcast<f32>(gpu_swap(vertex_buffer[addr + 0u], endian)),
                    bitcast<f32>(gpu_swap(vertex_buffer[addr + 1u], endian)),
                    bitcast<f32>(gpu_swap(vertex_buffer[addr + 2u], endian)),
                    1.0,
                );
            }
        }
        case VFMT_32_32_FLOAT: {
            if addr + 1u < n {
                result = vec4<f32>(
                    bitcast<f32>(gpu_swap(vertex_buffer[addr + 0u], endian)),
                    bitcast<f32>(gpu_swap(vertex_buffer[addr + 1u], endian)),
                    0.0,
                    1.0,
                );
            }
        }
        case VFMT_32_FLOAT: {
            if addr < n {
                result = vec4<f32>(
                    bitcast<f32>(gpu_swap(vertex_buffer[addr], endian)),
                    0.0, 0.0, 1.0,
                );
            }
        }
        case VFMT_8_8_8_8: {
            if addr < n {
                result = unpack4x8unorm(gpu_swap(vertex_buffer[addr], endian));
            }
        }
        case VFMT_16_16_FLOAT: {
            if addr < n {
                let h = unpack2x16float(gpu_swap(vertex_buffer[addr], endian));
                result = vec4<f32>(h.x, h.y, 0.0, 1.0);
            }
        }
        case VFMT_16_16_16_16_FLOAT: {
            if addr + 1u < n {
                let h0 = unpack2x16float(vertex_buffer[addr]);
                let h1 = unpack2x16float(vertex_buffer[addr + 1u]);
                result = vec4<f32>(h0.x, h0.y, h1.x, h1.y);
            }
        }
        case VFMT_16_16: {
            if addr < n {
                // Default to signed normalized; unsigned variants differ
                // only for sign-extension and are less common on Xenos VBs.
                let h = unpack2x16snorm(vertex_buffer[addr]);
                result = vec4<f32>(h.x, h.y, 0.0, 1.0);
            }
        }
        case VFMT_16_16_16_16: {
            if addr + 1u < n {
                let h0 = unpack2x16snorm(vertex_buffer[addr]);
                let h1 = unpack2x16snorm(vertex_buffer[addr + 1u]);
                result = vec4<f32>(h0.x, h0.y, h1.x, h1.y);
            }
        }
        case VFMT_2_10_10_10: {
            // Unpack 10-bit R/G/B (signed or unsigned, default unsigned
            // normalized) + 2-bit A. Unnormalized not exercised here.
            if addr < n {
                let packed = vertex_buffer[addr];
                let r = f32(packed & 0x3FFu) / 1023.0;
                let g = f32((packed >> 10u) & 0x3FFu) / 1023.0;
                let b = f32((packed >> 20u) & 0x3FFu) / 1023.0;
                let a = f32((packed >> 30u) & 0x3u) / 3.0;
                result = vec4<f32>(r, g, b, a);
            }
        }
        default: {
            reject_mask |= REJECT_VFETCH_FMT;
            // Identity fallback preserves vertex-index visibility.
            if addr < n {
                result = vec4<f32>(f32(vertex_buffer[addr]) * 0.0, 0.0, 0.0, 1.0);
            }
        }
    }

    registers[dst_reg & 0x7Fu] = result;
}

// Texture fetch. Reads (u, v) from the source register's .xy, selects the
// texture slot from the fetch instruction's fetch-constant index (dword0
// bits 20..24, matching `ucode::decode_fetch`), and writes the sample into
// the destination register. `textureSampleLevel` (explicit LOD) works in
// both VS and PS and does not require uniform control flow, so the per-slot
// `switch` is legal in the fragment stage.
fn interpret_texture_fetch(t: u32, is_vertex: bool) {
    var w0: u32 = 0u;
    if is_vertex {
        w0 = vs_instr_dword(t, 0u);
    } else {
        w0 = ps_instr_dword(t, 0u);
    }
    let dst_reg = (w0 >> 12u) & 0x3Fu;
    let src_reg = (w0 >> 5u) & 0x3Fu;
    let slot = (w0 >> 20u) & 0x1Fu;
    let uv = registers[src_reg & 0x3Fu].xy;
    var sample: vec4<f32>;
    switch slot {
        case 0u: { sample = textureSampleLevel(xenos_tex0, xenos_samp, uv, 0.0); }
        case 1u: { sample = textureSampleLevel(xenos_tex1, xenos_samp, uv, 0.0); }
        case 2u: { sample = textureSampleLevel(xenos_tex2, xenos_samp, uv, 0.0); }
        case 3u: { sample = textureSampleLevel(xenos_tex3, xenos_samp, uv, 0.0); }
        case 4u: { sample = textureSampleLevel(xenos_tex4, xenos_samp, uv, 0.0); }
        case 5u: { sample = textureSampleLevel(xenos_tex5, xenos_samp, uv, 0.0); }
        case 6u: { sample = textureSampleLevel(xenos_tex6, xenos_samp, uv, 0.0); }
        case 7u: { sample = textureSampleLevel(xenos_tex7, xenos_samp, uv, 0.0); }
        default: { sample = textureSampleLevel(xenos_tex0, xenos_samp, uv, 0.0); }
    }
    registers[dst_reg & 0x3Fu] = sample;
}

// Walk an Exec clause's instruction triples.
//   sequence: 2-bit-per-instruction bitmap. Bit 0 of a pair = fetch(1)/ALU(0);
//             bit 1 = serialize (ignored). (Xenos `ucode.h:226`.)
fn exec_vs(address: u32, count: u32, sequence: u32) {
    for (var i: u32 = 0u; i < count; i = i + 1u) {
        let t = address + i;
        let is_fetch = ((sequence >> (i * 2u)) & 1u) != 0u;
        if is_fetch {
            let opcode = vs_instr_dword(t, 0u) & 0x1Fu;
            // 0x00 = vertex fetch, 0x01 = texture fetch.
            if opcode == 0u {
                interpret_vertex_fetch(t);
            } else if opcode == 1u {
                interpret_texture_fetch(t, true);
            }
        } else {
            interpret_alu(t, true);
        }
    }
}
fn exec_ps(address: u32, count: u32, sequence: u32) {
    for (var i: u32 = 0u; i < count; i = i + 1u) {
        let t = address + i;
        let is_fetch = ((sequence >> (i * 2u)) & 1u) != 0u;
        if is_fetch {
            interpret_texture_fetch(t, false);
        } else {
            interpret_alu(t, false);
        }
    }
}

// Reset the per-invocation register state to a known baseline.
fn reset_state() {
    for (var i: u32 = 0u; i < 128u; i = i + 1u) {
        registers[i] = vec4<f32>(0.0, 0.0, 0.0, 0.0);
    }
    ps = 0.0;
    predicate = false;
    current_alloc = 0u;
    kill_flag = false;
    loop_depth = 0u;
    reject_mask = 0u;
    for (var i: u32 = 0u; i < 4u; i = i + 1u) {
        loop_counters[i] = 0u;
        loop_starts[i] = 0u;
    }
}

// ── Stage entry points.

// M7 register slots for exports. VS writes position at oPos (convention:
// Xenos export 0 within an `Alloc(Position)` range lands in registers[32])
// and a set of interpolators. We track both via `current_alloc`: writes
// inside each alloc range are tagged and copied out at Exit.
const OPOS_REG:       u32 = 32u;  // synthetic slot used by the interpreter
const OCOLOR_REG:     u32 = 33u;  // color0 scratch slot

@vertex
fn vs_main(@builtin(vertex_index) vidx: u32) -> VsOut {
    reset_state();

    // Seed r0 with the vertex index so simple shaders (or the procedural
    // fallback) have access without a real vertex fetch.
    registers[0] = vec4<f32>(f32(vidx), 0.0, 0.0, 1.0);
    // Seed the export slots with a procedural fallback: if the shader
    // never writes oPos / oColor, this keeps the output visible rather
    // than collapsing to (0,0) which would skip rasterization.
    let total = max(draw_ctx.vertex_count, 1u);
    let t_param = f32(vidx) / f32(total);
    let angle = t_param * 6.2831853;
    let radius = 0.35;
    registers[OPOS_REG] = vec4<f32>(cos(angle) * radius, sin(angle) * radius, 0.0, 1.0);
    let d = f32(draw_ctx.draw_index);
    registers[OCOLOR_REG] = vec4<f32>(
        0.5 + 0.5 * sin(d * 0.37),
        0.5 + 0.5 * sin(d * 0.51 + 2.0),
        0.5 + 0.5 * sin(d * 0.73 + 4.0),
        1.0,
    );

    // Dead-binding guard for VERTEX-stage-only vertex_buffer access.
    let vb_live = f32(vertex_buffer[0]) * 0.0;

    // Walk the VS CF table.
    walk_cf_vs();

    var out: VsOut;
    // Use registers[OPOS_REG] as position; the procedural fallback above
    // seeded it so an un-interpreted shader still draws a recognisable
    // circle.
    var opos = vec4<f32>(registers[OPOS_REG].xyz, registers[OPOS_REG].w);
    // iterate-3S: guest VS position → host clip space (see translator.rs). When
    // the transform is unset (procedural fallback) pass through unchanged.
    if (draw_ctx.ndc_scale.x != 0.0 || draw_ctx.ndc_scale.y != 0.0) {
        opos = vec4<f32>(opos.xy * draw_ctx.ndc_scale + draw_ctx.ndc_offset * opos.w, opos.z, opos.w);
    }
    out.position = opos;
    out.color = vec4<f32>(registers[OCOLOR_REG].rgb + vec3<f32>(vb_live), registers[OCOLOR_REG].a);
    return out;
}

@fragment
fn fs_main(in: VsOut) -> FsOut {
    reset_state();

    walk_cf_ps();

    // Kill: pixel shader `kill*` / `kills_*` opcodes set `kill_flag`;
    // `discard` at the entry level (outside any helper) is the only way to
    // guarantee early-out in WGSL.
    if kill_flag {
        discard;
    }

    var out: FsOut;
    out.color0 = in.color;
    return out;
}

// ── CF walker per stage. Shared logic: LOOP_START/LOOP_END iterate up to 16×
// from `xenos_consts.loop_consts[loop_id]`, COND_JMP honours the instruction's
// predicate flags, COND_CALL / RETURN set the reject mask (needs a call stack
// we don't have). A hard iteration cap keeps the GPU from hanging on
// malformed or extreme shaders.
const CF_WALKER_MAX_ITER: u32 = 4096u;

fn walk_cf_vs() {
    let cf_n = vs_cf_count();
    var cf_i: u32 = 0u;
    var iter: u32 = 0u;
    loop {
        if cf_i >= cf_n || iter >= CF_WALKER_MAX_ITER { break; }
        iter = iter + 1u;
        let kind = vs_cf_kind(cf_i);
        let primary = vs_cf_primary(cf_i);
        let aux = vs_cf_aux(cf_i);
        var advance: bool = true;
        var stop: bool = false;
        switch kind {
            case CF_KIND_EXEC, CF_KIND_EXEC_END: {
                let count = aux & 0xFFu;
                let sequence = aux >> 8u;
                exec_vs(primary, count, sequence);
                if kind == CF_KIND_EXEC_END { stop = true; }
            }
            case CF_KIND_ALLOC: { current_alloc = primary; }
            case CF_KIND_EXIT:  { stop = true; }
            case CF_KIND_LOOP_START: {
                let loop_id = aux & 0x1Fu;
                var loop_count = xenos_consts.loop_consts[loop_id] & 0xFFu;
                if loop_count > 16u {
                    loop_count = 16u;
                    reject_mask |= REJECT_LOOP_OVERFLOW;
                }
                if loop_count > 0u && loop_depth < 4u {
                    loop_starts[loop_depth] = cf_i;
                    loop_counters[loop_depth] = loop_count;
                    loop_depth = loop_depth + 1u;
                }
                // count==0 → fall through; matching LOOP_END will pop.
            }
            case CF_KIND_LOOP_END: {
                if loop_depth > 0u {
                    let d = loop_depth - 1u;
                    if loop_counters[d] > 1u {
                        loop_counters[d] = loop_counters[d] - 1u;
                        cf_i = loop_starts[d] + 1u;
                        advance = false;
                    } else {
                        loop_counters[d] = 0u;
                        loop_depth = d;
                    }
                }
            }
            case CF_KIND_COND_JMP: {
                let pred_bits = aux;
                let is_pred  = (pred_bits & 1u) != 0u;
                let pred_cnd = (pred_bits & 2u) != 0u;
                if !is_pred || predicate == pred_cnd {
                    cf_i = primary;
                    advance = false;
                }
            }
            case CF_KIND_COND_CALL, CF_KIND_RETURN: {
                // No call stack — mark and continue.
                reject_mask |= REJECT_CF_CALL;
            }
            case CF_KIND_NOP: {
                // kNop padding / kMarkVsFetchDone hint — no-op, just advance.
            }
            default: { reject_mask |= REJECT_CF_JUMP; }
        }
        if stop { break; }
        if advance { cf_i = cf_i + 1u; }
    }
}

fn walk_cf_ps() {
    let cf_n = ps_cf_count();
    var cf_i: u32 = 0u;
    var iter: u32 = 0u;
    loop {
        if cf_i >= cf_n || iter >= CF_WALKER_MAX_ITER { break; }
        iter = iter + 1u;
        let kind = ps_cf_kind(cf_i);
        let primary = ps_cf_primary(cf_i);
        let aux = ps_cf_aux(cf_i);
        var advance: bool = true;
        var stop: bool = false;
        switch kind {
            case CF_KIND_EXEC, CF_KIND_EXEC_END: {
                let count = aux & 0xFFu;
                let sequence = aux >> 8u;
                exec_ps(primary, count, sequence);
                if kind == CF_KIND_EXEC_END { stop = true; }
            }
            case CF_KIND_ALLOC: { current_alloc = primary; }
            case CF_KIND_EXIT:  { stop = true; }
            case CF_KIND_LOOP_START: {
                let loop_id = aux & 0x1Fu;
                var loop_count = xenos_consts.loop_consts[loop_id] & 0xFFu;
                if loop_count > 16u {
                    loop_count = 16u;
                    reject_mask |= REJECT_LOOP_OVERFLOW;
                }
                if loop_count > 0u && loop_depth < 4u {
                    loop_starts[loop_depth] = cf_i;
                    loop_counters[loop_depth] = loop_count;
                    loop_depth = loop_depth + 1u;
                }
            }
            case CF_KIND_LOOP_END: {
                if loop_depth > 0u {
                    let d = loop_depth - 1u;
                    if loop_counters[d] > 1u {
                        loop_counters[d] = loop_counters[d] - 1u;
                        cf_i = loop_starts[d] + 1u;
                        advance = false;
                    } else {
                        loop_counters[d] = 0u;
                        loop_depth = d;
                    }
                }
            }
            case CF_KIND_COND_JMP: {
                let pred_bits = aux;
                let is_pred  = (pred_bits & 1u) != 0u;
                let pred_cnd = (pred_bits & 2u) != 0u;
                if !is_pred || predicate == pred_cnd {
                    cf_i = primary;
                    advance = false;
                }
            }
            case CF_KIND_COND_CALL, CF_KIND_RETURN: {
                reject_mask |= REJECT_CF_CALL;
            }
            default: { reject_mask |= REJECT_CF_JUMP; }
        }
        if stop { break; }
        if advance { cf_i = cf_i + 1u; }
    }
}
