@neovand/zilion 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -145,7 +145,7 @@ Emulator bugs hide in undocumented corners, so Zilion's Z80 core is developed ag
145
145
 
146
146
  The core implements the full documented instruction set, the **CB**, **ED**, **DD/FD (IX/IY)**, and **DDCB/FDCB** prefix pages (including the undocumented DDCB register-copy side effect and the CPI/CPD undocumented-flag quirk), shadow registers, and `EXX`/`EX AF,AF'`.
147
147
 
148
- > **Scope & honesty:** Zilion is a batch execution core, not a cycle-accurate machine emulator. There are no interrupts, no I/O ports (`IN` reads 0, `OUT` is a no-op), and no cycle timing — every instruction advances the step counter by one. The `R` refresh register and exact `HALT` idle semantics are approximated. If you need cycle-accurate single-machine emulation, use a dedicated emulator; if you need to run a zillion Z80s fast, use Zilion.
148
+ > **Scope & honesty:** Zilion is a batch execution core, not a cycle-accurate machine emulator. There are no interrupts and no I/O ports (`IN` reads 0, `OUT` is a no-op), and time is counted in instructions, not T-states — every instruction advances the step counter by one. The `R` refresh register increments per M1 (opcode/prefix) fetch and `HALT` idles correctly (re-executing itself until the step budget runs out, matching a real Z80's PC/R behavior). If you need cycle-accurate single-machine emulation, use a dedicated emulator; if you need to run a zillion Z80s fast, use Zilion.
149
149
 
150
150
  ## Performance
151
151
 
@@ -1 +1 @@
1
- {"version":3,"file":"shader.d.ts","sourceRoot":"","sources":["../src/shader.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,uGAab,CAAC;AACX,eAAO,MAAM,gBAAgB,IAAoB,CAAC;AAElD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA2F3D"}
1
+ {"version":3,"file":"shader.d.ts","sourceRoot":"","sources":["../src/shader.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,uGAab,CAAC;AACX,eAAO,MAAM,gBAAgB,IAAoB,CAAC;AAElD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA4F3D"}
package/dist/shader.js CHANGED
@@ -88,6 +88,7 @@ fn main(@builtin(global_invocation_id) gid: vec3u) {
88
88
  cpu_h2 = (get16(rb, 11u) >> 8u) & 0xffu; cpu_l2 = get16(rb, 11u) & 0xffu;
89
89
  cpu_halted = 0u;
90
90
  cpu_iff1 = 0u; cpu_iff2 = 0u;
91
+ cpu_i = 0u; cpu_r = 0u;
91
92
  idx_mode = 0u; idx_disp = 0u; idx_uses_mem = 0u;
92
93
 
93
94
  // Run.
@@ -1 +1 @@
1
- {"version":3,"file":"shader.js","sourceRoot":"","sources":["../src/shader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;CACA,CAAC;AACX,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,qBAAqB;AAExE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IAClD,IAAI,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,6CAA6C,QAAQ,GAAG,CAAC,CAAC;IAC3E,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,GAAG,CAAC,CAAC;IAE1B,OAAO,UAAU,CAAC;;;;;;;;;;;;;;;+BAeY,QAAQ;oDACa,IAAI;iDACP,IAAI;;;EAGnD,aAAa;;;;;;;;;;;;;;;;;;;;;;iBAsBE,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0ChC,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"shader.js","sourceRoot":"","sources":["../src/shader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;CACA,CAAC;AACX,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,qBAAqB;AAExE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IAClD,IAAI,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,6CAA6C,QAAQ,GAAG,CAAC,CAAC;IAC3E,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,GAAG,CAAC,CAAC;IAE1B,OAAO,UAAU,CAAC;;;;;;;;;;;;;;;+BAeY,QAAQ;oDACa,IAAI;iDACP,IAAI;;;EAGnD,aAAa;;;;;;;;;;;;;;;;;;;;;;iBAsBE,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2ChC,CAAC;AACF,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const Z80_CORE_WGSL = "\n// === Z80 CPU State (per invocation) ===\nvar<private> cpu_a: u32;\nvar<private> cpu_f: u32;\nvar<private> cpu_b: u32;\nvar<private> cpu_c: u32;\nvar<private> cpu_d: u32;\nvar<private> cpu_e: u32;\nvar<private> cpu_h: u32;\nvar<private> cpu_l: u32;\nvar<private> cpu_sp: u32;\nvar<private> cpu_pc: u32;\nvar<private> cpu_a2: u32;\nvar<private> cpu_f2: u32;\nvar<private> cpu_b2: u32;\nvar<private> cpu_c2: u32;\nvar<private> cpu_d2: u32;\nvar<private> cpu_e2: u32;\nvar<private> cpu_h2: u32;\nvar<private> cpu_l2: u32;\nvar<private> cpu_halted: u32;\nvar<private> cpu_iff1: u32;\nvar<private> cpu_iff2: u32;\nvar<private> cpu_ix: u32;\nvar<private> cpu_iy: u32;\n// Index-prefix state for the instruction currently executing:\n// idx_mode: 0 = HL, 1 = IX, 2 = IY\n// idx_disp: sign-extended displacement for (IX+d)/(IY+d)\n// idx_uses_mem: 1 when this instruction dereferences (IX+d)/(IY+d), which\n// means H/L operands are NOT substituted by IXH/IXL (real Z80 rule).\nvar<private> idx_mode: u32;\nvar<private> idx_disp: u32;\nvar<private> idx_uses_mem: u32;\n\n// HOST CONTRACT: the host shader must declare the following BEFORE this core:\n// fn mem_read(addr: u32) -> u32 // read one byte from memory\n// fn mem_write(addr: u32, val: u32) // write one byte to memory\n// fn on_fetch_opcode(op: u32) -> bool // return true to skip (NOP) an\n// // opcode after prefix resolution\n// This lets the host choose the memory model (mask, modulo, storage buffer, \u2026)\n// and hook opcode execution (e.g. instruction suppression). See buildComputeShader.\n\n// Z80 flag bits\nconst CF: u32 = 0x01u;\nconst NF: u32 = 0x02u;\nconst PF: u32 = 0x04u;\nconst F3: u32 = 0x08u;\nconst HF: u32 = 0x10u;\nconst F5: u32 = 0x20u;\nconst ZF: u32 = 0x40u;\nconst SFl: u32 = 0x80u;\n\n\nfn z80_fetch() -> u32 {\n let val = mem_read(cpu_pc);\n cpu_pc = (cpu_pc + 1u) & 0xffffu;\n return val;\n}\n\nfn z80_fetch_word() -> u32 {\n let lo = z80_fetch();\n let hi = z80_fetch();\n return (hi << 8u) | lo;\n}\n\nfn z80_push16(val: u32) {\n cpu_sp = (cpu_sp - 1u) & 0xffffu;\n mem_write(cpu_sp, (val >> 8u) & 0xffu);\n cpu_sp = (cpu_sp - 1u) & 0xffffu;\n mem_write(cpu_sp, val & 0xffu);\n}\n\nfn z80_pop16() -> u32 {\n let lo = mem_read(cpu_sp);\n cpu_sp = (cpu_sp + 1u) & 0xffffu;\n let hi = mem_read(cpu_sp);\n cpu_sp = (cpu_sp + 1u) & 0xffffu;\n return (hi << 8u) | lo;\n}\n\nfn signed_byte(b: u32) -> i32 {\n let sb = i32(b);\n if (sb > 127) { return sb - 256; }\n return sb;\n}\n\n// === Register Access ===\nfn get_bc() -> u32 { return (cpu_b << 8u) | cpu_c; }\nfn get_de() -> u32 { return (cpu_d << 8u) | cpu_e; }\nfn get_hl() -> u32 { return (cpu_h << 8u) | cpu_l; }\nfn get_af() -> u32 { return (cpu_a << 8u) | cpu_f; }\n\nfn set_bc(v: u32) { cpu_b = (v >> 8u) & 0xffu; cpu_c = v & 0xffu; }\nfn set_de(v: u32) { cpu_d = (v >> 8u) & 0xffu; cpu_e = v & 0xffu; }\nfn set_hl(v: u32) { cpu_h = (v >> 8u) & 0xffu; cpu_l = v & 0xffu; }\nfn set_af(v: u32) { cpu_a = (v >> 8u) & 0xffu; cpu_f = v & 0xffu; }\n\nfn get_reg(idx: u32) -> u32 {\n switch(idx) {\n case 0u: { return cpu_b; }\n case 1u: { return cpu_c; }\n case 2u: { return cpu_d; }\n case 3u: { return cpu_e; }\n case 4u: {\n if (idx_mode != 0u && idx_uses_mem == 0u) { return (idx_reg16() >> 8u) & 0xffu; } // IXH/IYH\n return cpu_h;\n }\n case 5u: {\n if (idx_mode != 0u && idx_uses_mem == 0u) { return idx_reg16() & 0xffu; } // IXL/IYL\n return cpu_l;\n }\n case 6u: { return mem_read(idx_addr()); }\n case 7u: { return cpu_a; }\n default: { return 0u; }\n }\n}\n\nfn set_reg(idx: u32, val: u32) {\n let v = val & 0xffu;\n switch(idx) {\n case 0u: { cpu_b = v; }\n case 1u: { cpu_c = v; }\n case 2u: { cpu_d = v; }\n case 3u: { cpu_e = v; }\n case 4u: {\n if (idx_mode != 0u && idx_uses_mem == 0u) { set_idx_reg16((idx_reg16() & 0x00ffu) | (v << 8u)); }\n else { cpu_h = v; }\n }\n case 5u: {\n if (idx_mode != 0u && idx_uses_mem == 0u) { set_idx_reg16((idx_reg16() & 0xff00u) | v); }\n else { cpu_l = v; }\n }\n case 6u: { mem_write(idx_addr(), v); }\n case 7u: { cpu_a = v; }\n default: {}\n }\n}\n\nfn get_reg16(idx: u32) -> u32 {\n switch(idx) {\n case 0u: { return get_bc(); }\n case 1u: { return get_de(); }\n case 2u: { return idx_reg16(); } // HL / IX / IY\n case 3u: { return cpu_sp; }\n default: { return 0u; }\n }\n}\n\nfn set_reg16(idx: u32, val: u32) {\n let v = val & 0xffffu;\n switch(idx) {\n case 0u: { set_bc(v); }\n case 1u: { set_de(v); }\n case 2u: { set_idx_reg16(v); } // HL / IX / IY\n case 3u: { cpu_sp = v; }\n default: {}\n }\n}\n\nfn get_reg16_af(idx: u32) -> u32 {\n if (idx == 3u) { return get_af(); }\n return get_reg16(idx);\n}\n\nfn set_reg16_af(idx: u32, val: u32) {\n if (idx == 3u) { set_af(val); } else { set_reg16(idx, val); }\n}\n\n// === Index register (IX/IY) helpers ===\n// The 16-bit register the current prefix maps HL to (HL itself when no prefix).\nfn idx_reg16() -> u32 {\n if (idx_mode == 1u) { return cpu_ix; }\n if (idx_mode == 2u) { return cpu_iy; }\n return get_hl();\n}\nfn set_idx_reg16(v: u32) {\n if (idx_mode == 1u) { cpu_ix = v & 0xffffu; }\n else if (idx_mode == 2u) { cpu_iy = v & 0xffffu; }\n else { set_hl(v); }\n}\n// Address used for (HL) / (IX+d) / (IY+d).\nfn idx_addr() -> u32 {\n if (idx_mode != 0u) { return (idx_reg16() + idx_disp) & 0xffffu; }\n return get_hl();\n}\n// Sign-extend a displacement byte to 16 bits (two's complement).\nfn signext(b: u32) -> u32 {\n if (b >= 0x80u) { return b | 0xff00u; }\n return b;\n}\n// Does this main opcode dereference (HL)? (Determines displacement fetch and\n// whether H/L operands are IXH/IXL or real H/L under a DD/FD prefix.)\nfn op_uses_hl_mem(op: u32) -> bool {\n let x = (op >> 6u) & 3u;\n let y = (op >> 3u) & 7u;\n let z = op & 7u;\n if (x == 1u) { return (y == 6u || z == 6u) && !(y == 6u && z == 6u); } // LD r,(HL)/(HL),r (not HALT)\n if (x == 2u) { return z == 6u; } // ALU A,(HL)\n if (x == 0u) {\n if (z == 4u || z == 5u || z == 6u) { return y == 6u; } // INC/DEC (HL), LD (HL),n\n return false;\n }\n return false;\n}\n// Write a REAL 8-bit register (no IX/IY substitution) \u2014 used by the\n// undocumented DDCB/FDCB register-copy side effect.\nfn set_reg_raw(idx: u32, val: u32) {\n let v = val & 0xffu;\n switch(idx) {\n case 0u: { cpu_b = v; }\n case 1u: { cpu_c = v; }\n case 2u: { cpu_d = v; }\n case 3u: { cpu_e = v; }\n case 4u: { cpu_h = v; }\n case 5u: { cpu_l = v; }\n case 7u: { cpu_a = v; }\n default: {}\n }\n}\n\n// === Flag Helpers ===\nfn sz_flags(val: u32) -> u32 {\n var f = val & SFl;\n if (val == 0u) { f |= ZF; }\n f |= val & (F3 | F5);\n return f;\n}\n\nfn parity(val: u32) -> bool {\n var p = val;\n p ^= p >> 4u;\n p ^= p >> 2u;\n p ^= p >> 1u;\n return (p & 1u) == 0u;\n}\n\nfn check_cc(cc: u32) -> bool {\n switch(cc) {\n case 0u: { return (cpu_f & ZF) == 0u; }\n case 1u: { return (cpu_f & ZF) != 0u; }\n case 2u: { return (cpu_f & CF) == 0u; }\n case 3u: { return (cpu_f & CF) != 0u; }\n case 4u: { return (cpu_f & PF) == 0u; }\n case 5u: { return (cpu_f & PF) != 0u; }\n case 6u: { return (cpu_f & SFl) == 0u; }\n case 7u: { return (cpu_f & SFl) != 0u; }\n default: { return false; }\n }\n}\n\n// === ALU ===\nfn z80_alu(op: u32, val: u32) {\n let a = cpu_a;\n let c = cpu_f & CF;\n switch(op) {\n case 0u: { // ADD\n let r = a + val;\n cpu_f = sz_flags(r & 0xffu) | select(0u, CF, r > 0xffu) |\n ((a ^ val ^ r) & HF) |\n select(0u, PF, ((~(a ^ val)) & (a ^ r) & 0x80u) != 0u);\n cpu_a = r & 0xffu;\n }\n case 1u: { // ADC\n let r = a + val + c;\n cpu_f = sz_flags(r & 0xffu) | select(0u, CF, r > 0xffu) |\n ((a ^ val ^ r) & HF) |\n select(0u, PF, ((~(a ^ val)) & (a ^ r) & 0x80u) != 0u);\n cpu_a = r & 0xffu;\n }\n case 2u: { // SUB\n let r = i32(a) - i32(val);\n let ru = u32(r) & 0xffu;\n cpu_f = sz_flags(ru) | NF | select(0u, CF, r < 0) |\n ((a ^ val ^ u32(r)) & HF) |\n select(0u, PF, (((a ^ val) & (a ^ u32(r))) & 0x80u) != 0u);\n cpu_a = ru;\n }\n case 3u: { // SBC\n let r = i32(a) - i32(val) - i32(c);\n let ru = u32(r) & 0xffu;\n cpu_f = sz_flags(ru) | NF | select(0u, CF, r < 0) |\n ((a ^ val ^ u32(r)) & HF) |\n select(0u, PF, (((a ^ val) & (a ^ u32(r))) & 0x80u) != 0u);\n cpu_a = ru;\n }\n case 4u: { // AND\n cpu_a = a & val;\n cpu_f = sz_flags(cpu_a) | HF | select(0u, PF, parity(cpu_a));\n }\n case 5u: { // XOR\n cpu_a = a ^ val;\n cpu_f = sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));\n }\n case 6u: { // OR\n cpu_a = a | val;\n cpu_f = sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));\n }\n case 7u: { // CP\n let r = i32(a) - i32(val);\n let ru = u32(r) & 0xffu;\n cpu_f = (ru & SFl) | select(0u, ZF, ru == 0u) | (val & (F3 | F5)) | NF |\n select(0u, CF, r < 0) | ((a ^ val ^ u32(r)) & HF) |\n select(0u, PF, (((a ^ val) & (a ^ u32(r))) & 0x80u) != 0u);\n }\n default: {}\n }\n}\n\nfn z80_inc8(val: u32) -> u32 {\n let r = (val + 1u) & 0xffu;\n cpu_f = (cpu_f & CF) | sz_flags(r) |\n select(0u, PF, val == 0x7fu) |\n select(0u, HF, (r & 0x0fu) == 0u);\n return r;\n}\n\nfn z80_dec8(val: u32) -> u32 {\n let r = (val - 1u) & 0xffu;\n cpu_f = (cpu_f & CF) | sz_flags(r) | NF |\n select(0u, PF, val == 0x80u) |\n select(0u, HF, (val & 0x0fu) == 0u);\n return r;\n}\n\nfn z80_add_hl(val: u32) {\n let hl = idx_reg16(); // ADD HL,rp / ADD IX,rp / ADD IY,rp\n let r = hl + val;\n cpu_f = (cpu_f & (SFl | ZF | PF)) |\n select(0u, CF, r > 0xffffu) |\n select(0u, HF, ((hl ^ val ^ r) & 0x1000u) != 0u) |\n ((r >> 8u) & (F3 | F5));\n set_idx_reg16(r & 0xffffu);\n}\n\n// === Rotate/Shift for accumulator ===\nfn z80_rot_accum(y: u32) {\n let a = cpu_a;\n let c = cpu_f & CF;\n let keep = cpu_f & (SFl | ZF | PF);\n switch(y) {\n case 0u: { // RLCA\n cpu_a = ((a << 1u) | (a >> 7u)) & 0xffu;\n cpu_f = keep | (a >> 7u) | (cpu_a & (F3 | F5));\n }\n case 1u: { // RRCA\n cpu_a = ((a >> 1u) | (a << 7u)) & 0xffu;\n cpu_f = keep | (a & 1u) | (cpu_a & (F3 | F5));\n }\n case 2u: { // RLA\n cpu_a = ((a << 1u) | c) & 0xffu;\n cpu_f = keep | (a >> 7u) | (cpu_a & (F3 | F5));\n }\n case 3u: { // RRA\n cpu_a = ((a >> 1u) | (c << 7u)) & 0xffu;\n cpu_f = keep | (a & 1u) | (cpu_a & (F3 | F5));\n }\n case 4u: { // DAA\n var correction = 0u;\n var carry = c;\n if ((cpu_f & HF) != 0u || (a & 0x0fu) > 9u) { correction |= 0x06u; }\n if (c != 0u || a > 0x99u) { correction |= 0x60u; carry = 1u; }\n if ((cpu_f & NF) != 0u) { cpu_a = (a - correction) & 0xffu; }\n else { cpu_a = (a + correction) & 0xffu; }\n cpu_f = (cpu_f & NF) | sz_flags(cpu_a) | carry |\n ((a ^ cpu_a) & HF) | select(0u, PF, parity(cpu_a));\n }\n case 5u: { // CPL\n cpu_a = (~a) & 0xffu;\n cpu_f = (cpu_f & (SFl | ZF | PF | CF)) | HF | NF | (cpu_a & (F3 | F5));\n }\n case 6u: { // SCF\n cpu_f = (cpu_f & (SFl | ZF | PF)) | CF | (cpu_a & (F3 | F5));\n }\n case 7u: { // CCF\n cpu_f = (cpu_f & (SFl | ZF | PF)) |\n select(0u, HF, c != 0u) |\n select(CF, 0u, c != 0u) |\n (cpu_a & (F3 | F5));\n }\n default: {}\n }\n}\n\n// === CB Prefix (bit ops, rotates, shifts) ===\nfn z80_cb_rot(op: u32, val: u32) -> u32 {\n let c = cpu_f & CF;\n var r = 0u;\n switch(op) {\n case 0u: { r = ((val << 1u) | (val >> 7u)) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }\n case 1u: { r = ((val >> 1u) | (val << 7u)) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }\n case 2u: { r = ((val << 1u) | c) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }\n case 3u: { r = ((val >> 1u) | (c << 7u)) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }\n case 4u: { r = (val << 1u) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }\n case 5u: { r = ((val >> 1u) | (val & 0x80u)) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }\n case 6u: { r = ((val << 1u) | 1u) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }\n case 7u: { r = (val >> 1u) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }\n default: { r = val; }\n }\n return r;\n}\n\nfn z80_exec_cb() {\n let op = z80_fetch();\n let x = (op >> 6u) & 3u;\n let y = (op >> 3u) & 7u;\n let z = op & 7u;\n let val = get_reg(z);\n switch(x) {\n case 0u: { set_reg(z, z80_cb_rot(y, val)); }\n case 1u: { // BIT\n cpu_f = (cpu_f & CF) | HF |\n select(0u, ZF | PF, (val & (1u << y)) == 0u) |\n select(0u, SFl, y == 7u && (val & 0x80u) != 0u) |\n (val & (F3 | F5));\n }\n case 2u: { set_reg(z, val & ~(1u << y)); }\n case 3u: { set_reg(z, val | (1u << y)); }\n default: {}\n }\n}\n\n// DDCB / FDCB: operates on (IX+d)/(IY+d). The displacement (idx_disp) has\n// already been fetched. For rot/shift/RES/SET the result is written to memory\n// AND (undocumented) copied to the real register in the low 3 bits unless it is\n// 6. For BIT, the undocumented F3/F5 come from the high byte of the address.\nfn z80_exec_idxcb(cbop: u32) {\n let addr = (idx_reg16() + idx_disp) & 0xffffu;\n let cx = (cbop >> 6u) & 3u;\n let cy = (cbop >> 3u) & 7u;\n let cz = cbop & 7u;\n let val = mem_read(addr);\n switch(cx) {\n case 0u: {\n let r = z80_cb_rot(cy, val);\n mem_write(addr, r);\n if (cz != 6u) { set_reg_raw(cz, r); }\n }\n case 1u: { // BIT n,(IX+d)\n cpu_f = (cpu_f & CF) | HF |\n select(0u, ZF | PF, (val & (1u << cy)) == 0u) |\n select(0u, SFl, cy == 7u && (val & 0x80u) != 0u) |\n ((addr >> 8u) & (F3 | F5));\n }\n case 2u: {\n let r = val & ~(1u << cy);\n mem_write(addr, r);\n if (cz != 6u) { set_reg_raw(cz, r); }\n }\n case 3u: {\n let r = val | (1u << cy);\n mem_write(addr, r);\n if (cz != 6u) { set_reg_raw(cz, r); }\n }\n default: {}\n }\n}\n\n// === Block Transfer (ED prefix) ===\nfn z80_ldi() {\n let val = mem_read(get_hl());\n mem_write(get_de(), val);\n set_hl((get_hl() + 1u) & 0xffffu);\n set_de((get_de() + 1u) & 0xffffu);\n set_bc((get_bc() - 1u) & 0xffffu);\n let n = (val + cpu_a) & 0xffu;\n cpu_f = (cpu_f & (SFl | ZF | CF)) |\n select(0u, PF, get_bc() != 0u) |\n (n & F3) | select(0u, F5, (n & 0x02u) != 0u);\n}\n\nfn z80_ldd() {\n let val = mem_read(get_hl());\n mem_write(get_de(), val);\n set_hl((get_hl() - 1u) & 0xffffu);\n set_de((get_de() - 1u) & 0xffffu);\n set_bc((get_bc() - 1u) & 0xffffu);\n let n = (val + cpu_a) & 0xffu;\n cpu_f = (cpu_f & (SFl | ZF | CF)) |\n select(0u, PF, get_bc() != 0u) |\n (n & F3) | select(0u, F5, (n & 0x02u) != 0u);\n}\n\nfn z80_cpi() {\n let val = mem_read(get_hl());\n let r = (cpu_a - val) & 0xffu;\n let hf = (cpu_a ^ val ^ r) & HF;\n // Undocumented F3/F5 come from n = A-(HL)-HF (bit 3 -> F3, bit 1 -> F5).\n let n = (r - select(0u, 1u, hf != 0u)) & 0xffu;\n set_hl((get_hl() + 1u) & 0xffffu);\n set_bc((get_bc() - 1u) & 0xffffu);\n cpu_f = (cpu_f & CF) | (r & SFl) | select(0u, ZF, r == 0u) | NF |\n hf | select(0u, PF, get_bc() != 0u) |\n (n & F3) | ((n & 0x02u) << 4u);\n}\n\nfn z80_cpd() {\n let val = mem_read(get_hl());\n let r = (cpu_a - val) & 0xffu;\n let hf = (cpu_a ^ val ^ r) & HF;\n let n = (r - select(0u, 1u, hf != 0u)) & 0xffu;\n set_hl((get_hl() - 1u) & 0xffffu);\n set_bc((get_bc() - 1u) & 0xffffu);\n cpu_f = (cpu_f & CF) | (r & SFl) | select(0u, ZF, r == 0u) | NF |\n hf | select(0u, PF, get_bc() != 0u) |\n (n & F3) | ((n & 0x02u) << 4u);\n}\n\n// === ED Prefix ===\nfn z80_exec_ed() {\n let op = z80_fetch();\n switch(op) {\n case 0xa0u: { z80_ldi(); }\n case 0xa8u: { z80_ldd(); }\n case 0xb0u: { z80_ldi(); if (get_bc() != 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } } // LDIR\n case 0xb8u: { z80_ldd(); if (get_bc() != 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } } // LDDR\n case 0xa1u: { z80_cpi(); }\n case 0xa9u: { z80_cpd(); }\n case 0xb1u: { z80_cpi(); if (get_bc() != 0u && (cpu_f & ZF) == 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } }\n case 0xb9u: { z80_cpd(); if (get_bc() != 0u && (cpu_f & ZF) == 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } }\n // NEG\n case 0x44u, 0x4cu, 0x54u, 0x5cu, 0x64u, 0x6cu, 0x74u, 0x7cu: {\n let a = cpu_a; cpu_a = 0u; z80_alu(2u, a);\n }\n // RETN/RETI\n case 0x45u, 0x4du, 0x55u, 0x5du, 0x65u, 0x6du, 0x75u, 0x7du: {\n cpu_iff1 = cpu_iff2; cpu_pc = z80_pop16();\n }\n // LD I,A / LD R,A / LD A,I / LD A,R\n case 0x47u: {} // LD I,A - no I register in our sim\n case 0x4fu: {} // LD R,A\n // LD A,I / LD A,R: I and R are not modelled (treated as 0), so A becomes 0.\n // Flags: S/Z from the loaded value, PF = IFF2, N/H reset, C preserved.\n case 0x57u: { cpu_a = 0u; cpu_f = (cpu_f & CF) | sz_flags(0u) | select(0u, PF, cpu_iff2 != 0u); }\n case 0x5fu: { cpu_a = 0u; cpu_f = (cpu_f & CF) | sz_flags(0u) | select(0u, PF, cpu_iff2 != 0u); }\n // LD (nn), rr\n case 0x43u, 0x53u, 0x63u, 0x73u: {\n let nn = z80_fetch_word();\n let rp = (op >> 4u) & 3u;\n let val = get_reg16(rp);\n mem_write(nn, val & 0xffu);\n mem_write((nn + 1u) & 0xffffu, (val >> 8u) & 0xffu);\n }\n // LD rr, (nn)\n case 0x4bu, 0x5bu, 0x6bu, 0x7bu: {\n let nn = z80_fetch_word();\n let rp = (op >> 4u) & 3u;\n let lo = mem_read(nn);\n let hi = mem_read((nn + 1u) & 0xffffu);\n set_reg16(rp, (hi << 8u) | lo);\n }\n // ADC HL, rr\n case 0x4au, 0x5au, 0x6au, 0x7au: {\n let rp = (op >> 4u) & 3u;\n let hl = get_hl();\n let val = get_reg16(rp);\n let c = cpu_f & CF;\n let r = hl + val + c;\n cpu_f = ((r >> 8u) & SFl) | select(0u, ZF, (r & 0xffffu) == 0u) |\n select(0u, HF, ((hl ^ val ^ r) & 0x1000u) != 0u) |\n select(0u, PF, ((~(hl ^ val)) & (hl ^ r) & 0x8000u) != 0u) |\n select(0u, CF, r > 0xffffu) | ((r >> 8u) & (F3 | F5));\n set_hl(r & 0xffffu);\n }\n // SBC HL, rr\n case 0x42u, 0x52u, 0x62u, 0x72u: {\n let rp = (op >> 4u) & 3u;\n let hl = get_hl();\n let val = get_reg16(rp);\n let c = cpu_f & CF;\n let r = i32(hl) - i32(val) - i32(c);\n let ru = u32(r) & 0xffffu;\n cpu_f = ((ru >> 8u) & SFl) | select(0u, ZF, ru == 0u) | NF |\n select(0u, HF, ((hl ^ val ^ u32(r)) & 0x1000u) != 0u) |\n select(0u, PF, (((hl ^ val) & (hl ^ u32(r))) & 0x8000u) != 0u) |\n select(0u, CF, r < 0) | ((ru >> 8u) & (F3 | F5));\n set_hl(ru);\n }\n // RRD\n case 0x67u: {\n let m = mem_read(get_hl());\n mem_write(get_hl(), ((cpu_a << 4u) | (m >> 4u)) & 0xffu);\n cpu_a = (cpu_a & 0xf0u) | (m & 0x0fu);\n cpu_f = (cpu_f & CF) | sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));\n }\n // RLD\n case 0x6fu: {\n let m = mem_read(get_hl());\n mem_write(get_hl(), ((m << 4u) | (cpu_a & 0x0fu)) & 0xffu);\n cpu_a = (cpu_a & 0xf0u) | (m >> 4u);\n cpu_f = (cpu_f & CF) | sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));\n }\n // IN r,(C) - simplified, just set to 0\n case 0x40u, 0x48u, 0x50u, 0x58u, 0x60u, 0x68u, 0x70u, 0x78u: {\n set_reg((op >> 3u) & 7u, 0u);\n }\n default: {} // unknown ED ops = NOP\n }\n}\n\n// === Main Opcode Execution ===\nfn z80_exec_x0(y: u32, z: u32, p: u32, q: u32) {\n switch(z) {\n case 0u: {\n switch(y) {\n case 0u: {} // NOP\n case 1u: { // EX AF,AF'\n var t = cpu_a; cpu_a = cpu_a2; cpu_a2 = t;\n t = cpu_f; cpu_f = cpu_f2; cpu_f2 = t;\n }\n case 2u: { // DJNZ\n let d = signed_byte(z80_fetch());\n cpu_b = (cpu_b - 1u) & 0xffu;\n if (cpu_b != 0u) { cpu_pc = u32(i32(cpu_pc) + d) & 0xffffu; }\n }\n case 3u: { // JR\n let d = signed_byte(z80_fetch());\n cpu_pc = u32(i32(cpu_pc) + d) & 0xffffu;\n }\n default: { // JR cc (y-4)\n let d = signed_byte(z80_fetch());\n if (check_cc(y - 4u)) { cpu_pc = u32(i32(cpu_pc) + d) & 0xffffu; }\n }\n }\n }\n case 1u: {\n if (q == 0u) { set_reg16(p, z80_fetch_word()); }\n else { z80_add_hl(get_reg16(p)); }\n }\n case 2u: {\n if (q == 0u) {\n switch(p) {\n case 0u: { mem_write(get_bc(), cpu_a); }\n case 1u: { mem_write(get_de(), cpu_a); }\n case 2u: { let nn = z80_fetch_word(); let hl = idx_reg16(); mem_write(nn, hl & 0xffu); mem_write((nn+1u) & 0xffffu, (hl >> 8u) & 0xffu); }\n case 3u: { mem_write(z80_fetch_word(), cpu_a); }\n default: {}\n }\n } else {\n switch(p) {\n case 0u: { cpu_a = mem_read(get_bc()); }\n case 1u: { cpu_a = mem_read(get_de()); }\n case 2u: { let nn = z80_fetch_word(); let lo = mem_read(nn); let hi = mem_read((nn+1u) & 0xffffu); set_idx_reg16((hi << 8u) | lo); }\n case 3u: { cpu_a = mem_read(z80_fetch_word()); }\n default: {}\n }\n }\n }\n case 3u: {\n if (q == 0u) { set_reg16(p, (get_reg16(p) + 1u) & 0xffffu); }\n else { set_reg16(p, (get_reg16(p) - 1u) & 0xffffu); }\n }\n case 4u: { set_reg(y, z80_inc8(get_reg(y))); }\n case 5u: { set_reg(y, z80_dec8(get_reg(y))); }\n case 6u: { set_reg(y, z80_fetch()); }\n case 7u: { z80_rot_accum(y); }\n default: {}\n }\n}\n\nfn z80_exec_x3(y: u32, z: u32, p: u32, q: u32) {\n switch(z) {\n case 0u: { if (check_cc(y)) { cpu_pc = z80_pop16(); } }\n case 1u: {\n if (q == 0u) { set_reg16_af(p, z80_pop16()); }\n else {\n switch(p) {\n case 0u: { cpu_pc = z80_pop16(); } // RET\n case 1u: { // EXX\n var t = cpu_b; cpu_b = cpu_b2; cpu_b2 = t;\n t = cpu_c; cpu_c = cpu_c2; cpu_c2 = t;\n t = cpu_d; cpu_d = cpu_d2; cpu_d2 = t;\n t = cpu_e; cpu_e = cpu_e2; cpu_e2 = t;\n t = cpu_h; cpu_h = cpu_h2; cpu_h2 = t;\n t = cpu_l; cpu_l = cpu_l2; cpu_l2 = t;\n }\n case 2u: { cpu_pc = idx_reg16(); } // JP (HL)/(IX)/(IY)\n case 3u: { cpu_sp = idx_reg16(); } // LD SP,HL/IX/IY\n default: {}\n }\n }\n }\n case 2u: { let nn = z80_fetch_word(); if (check_cc(y)) { cpu_pc = nn; } }\n case 3u: {\n switch(y) {\n case 0u: { cpu_pc = z80_fetch_word(); } // JP nn\n case 1u: { z80_exec_cb(); }\n case 2u: { z80_fetch(); } // OUT (n),A - no I/O device, discard (matches zff outPort no-op)\n case 3u: { z80_fetch(); cpu_a = 0u; } // IN A,(n) - no I/O device, reads 0 (matches zff inPort\u21920)\n case 4u: { // EX (SP),HL / EX (SP),IX / EX (SP),IY\n let lo = mem_read(cpu_sp);\n let hi = mem_read((cpu_sp + 1u) & 0xffffu);\n let hl = idx_reg16();\n mem_write(cpu_sp, hl & 0xffu);\n mem_write((cpu_sp + 1u) & 0xffffu, (hl >> 8u) & 0xffu);\n set_idx_reg16((hi << 8u) | lo);\n }\n case 5u: { // EX DE,HL\n let td = cpu_d; let te = cpu_e;\n cpu_d = cpu_h; cpu_e = cpu_l;\n cpu_h = td; cpu_l = te;\n }\n case 6u: { cpu_iff1 = 0u; cpu_iff2 = 0u; }\n case 7u: { cpu_iff1 = 1u; cpu_iff2 = 1u; }\n default: {}\n }\n }\n case 4u: { let nn = z80_fetch_word(); if (check_cc(y)) { z80_push16(cpu_pc); cpu_pc = nn; } }\n case 5u: {\n if (q == 0u) { z80_push16(get_reg16_af(p)); }\n else {\n switch(p) {\n case 0u: { let nn = z80_fetch_word(); z80_push16(cpu_pc); cpu_pc = nn; } // CALL nn\n case 1u, 3u: {} // DD/FD prefix handled in z80_step\n case 2u: { z80_exec_ed(); }\n default: {}\n }\n }\n }\n case 6u: { z80_alu(y, z80_fetch()); }\n case 7u: { z80_push16(cpu_pc); cpu_pc = y * 8u; } // RST\n default: {}\n }\n}\n\nfn z80_execute(op: u32) {\n let x = (op >> 6u) & 3u;\n let y = (op >> 3u) & 7u;\n let z = op & 7u;\n let p = (y >> 1u) & 3u;\n let q = y & 1u;\n switch(x) {\n case 0u: { z80_exec_x0(y, z, p, q); }\n case 1u: {\n if (y == 6u && z == 6u) { cpu_halted = 1u; }\n else { set_reg(y, get_reg(z)); }\n }\n case 2u: { z80_alu(y, get_reg(z)); }\n case 3u: { z80_exec_x3(y, z, p, q); }\n default: {}\n }\n}\n\nfn z80_step() {\n if (cpu_halted != 0u) { return; }\n // Reset per-instruction index-prefix state.\n idx_mode = 0u;\n idx_uses_mem = 0u;\n idx_disp = 0u;\n\n var op = z80_fetch();\n // A DD/FD prefix selects IX/IY for the following opcode.\n if (op == 0xddu || op == 0xfdu) {\n idx_mode = select(2u, 1u, op == 0xddu);\n let next = z80_fetch();\n // A prefix immediately followed by another prefix or ED is a wasted M1:\n // this step consumes just the prefix; back up so the next step restarts\n // at the following byte (matches real Z80 timing and avoids an\n // unbounded fetch loop on all-prefix programs).\n if (next == 0xddu || next == 0xfdu || next == 0xedu) {\n cpu_pc = (cpu_pc - 1u) & 0xffffu;\n return;\n }\n op = next;\n }\n\n // Host hook: skip execution (treat as NOP) if requested.\n if (on_fetch_opcode(op)) { return; }\n\n if (idx_mode != 0u) {\n if (op == 0xcbu) {\n // DDCB/FDCB: displacement precedes the CB opcode.\n idx_disp = signext(z80_fetch());\n let cbop = z80_fetch();\n z80_exec_idxcb(cbop);\n return;\n }\n if (op_uses_hl_mem(op)) {\n idx_uses_mem = 1u;\n idx_disp = signext(z80_fetch());\n }\n }\n z80_execute(op);\n}\n";
1
+ export declare const Z80_CORE_WGSL = "\n// === Z80 CPU State (per invocation) ===\nvar<private> cpu_a: u32;\nvar<private> cpu_f: u32;\nvar<private> cpu_b: u32;\nvar<private> cpu_c: u32;\nvar<private> cpu_d: u32;\nvar<private> cpu_e: u32;\nvar<private> cpu_h: u32;\nvar<private> cpu_l: u32;\nvar<private> cpu_sp: u32;\nvar<private> cpu_pc: u32;\nvar<private> cpu_a2: u32;\nvar<private> cpu_f2: u32;\nvar<private> cpu_b2: u32;\nvar<private> cpu_c2: u32;\nvar<private> cpu_d2: u32;\nvar<private> cpu_e2: u32;\nvar<private> cpu_h2: u32;\nvar<private> cpu_l2: u32;\nvar<private> cpu_halted: u32;\nvar<private> cpu_iff1: u32;\nvar<private> cpu_iff2: u32;\nvar<private> cpu_ix: u32;\nvar<private> cpu_iy: u32;\nvar<private> cpu_i: u32; // interrupt vector register (I)\nvar<private> cpu_r: u32; // memory refresh register (R); bits 0-6 count M1 cycles, bit 7 preserved\n\n// Increment R once per M1 (opcode/prefix) fetch: 7-bit counter, bit 7 preserved.\nfn r_inc() { cpu_r = (cpu_r & 0x80u) | ((cpu_r + 1u) & 0x7fu); }\n// Index-prefix state for the instruction currently executing:\n// idx_mode: 0 = HL, 1 = IX, 2 = IY\n// idx_disp: sign-extended displacement for (IX+d)/(IY+d)\n// idx_uses_mem: 1 when this instruction dereferences (IX+d)/(IY+d), which\n// means H/L operands are NOT substituted by IXH/IXL (real Z80 rule).\nvar<private> idx_mode: u32;\nvar<private> idx_disp: u32;\nvar<private> idx_uses_mem: u32;\n\n// HOST CONTRACT: the host shader must declare the following BEFORE this core:\n// fn mem_read(addr: u32) -> u32 // read one byte from memory\n// fn mem_write(addr: u32, val: u32) // write one byte to memory\n// fn on_fetch_opcode(op: u32) -> bool // return true to skip (NOP) an\n// // opcode after prefix resolution\n// This lets the host choose the memory model (mask, modulo, storage buffer, \u2026)\n// and hook opcode execution (e.g. instruction suppression). See buildComputeShader.\n\n// Z80 flag bits\nconst CF: u32 = 0x01u;\nconst NF: u32 = 0x02u;\nconst PF: u32 = 0x04u;\nconst F3: u32 = 0x08u;\nconst HF: u32 = 0x10u;\nconst F5: u32 = 0x20u;\nconst ZF: u32 = 0x40u;\nconst SFl: u32 = 0x80u;\n\n\nfn z80_fetch() -> u32 {\n let val = mem_read(cpu_pc);\n cpu_pc = (cpu_pc + 1u) & 0xffffu;\n return val;\n}\n\nfn z80_fetch_word() -> u32 {\n let lo = z80_fetch();\n let hi = z80_fetch();\n return (hi << 8u) | lo;\n}\n\nfn z80_push16(val: u32) {\n cpu_sp = (cpu_sp - 1u) & 0xffffu;\n mem_write(cpu_sp, (val >> 8u) & 0xffu);\n cpu_sp = (cpu_sp - 1u) & 0xffffu;\n mem_write(cpu_sp, val & 0xffu);\n}\n\nfn z80_pop16() -> u32 {\n let lo = mem_read(cpu_sp);\n cpu_sp = (cpu_sp + 1u) & 0xffffu;\n let hi = mem_read(cpu_sp);\n cpu_sp = (cpu_sp + 1u) & 0xffffu;\n return (hi << 8u) | lo;\n}\n\nfn signed_byte(b: u32) -> i32 {\n let sb = i32(b);\n if (sb > 127) { return sb - 256; }\n return sb;\n}\n\n// === Register Access ===\nfn get_bc() -> u32 { return (cpu_b << 8u) | cpu_c; }\nfn get_de() -> u32 { return (cpu_d << 8u) | cpu_e; }\nfn get_hl() -> u32 { return (cpu_h << 8u) | cpu_l; }\nfn get_af() -> u32 { return (cpu_a << 8u) | cpu_f; }\n\nfn set_bc(v: u32) { cpu_b = (v >> 8u) & 0xffu; cpu_c = v & 0xffu; }\nfn set_de(v: u32) { cpu_d = (v >> 8u) & 0xffu; cpu_e = v & 0xffu; }\nfn set_hl(v: u32) { cpu_h = (v >> 8u) & 0xffu; cpu_l = v & 0xffu; }\nfn set_af(v: u32) { cpu_a = (v >> 8u) & 0xffu; cpu_f = v & 0xffu; }\n\nfn get_reg(idx: u32) -> u32 {\n switch(idx) {\n case 0u: { return cpu_b; }\n case 1u: { return cpu_c; }\n case 2u: { return cpu_d; }\n case 3u: { return cpu_e; }\n case 4u: {\n if (idx_mode != 0u && idx_uses_mem == 0u) { return (idx_reg16() >> 8u) & 0xffu; } // IXH/IYH\n return cpu_h;\n }\n case 5u: {\n if (idx_mode != 0u && idx_uses_mem == 0u) { return idx_reg16() & 0xffu; } // IXL/IYL\n return cpu_l;\n }\n case 6u: { return mem_read(idx_addr()); }\n case 7u: { return cpu_a; }\n default: { return 0u; }\n }\n}\n\nfn set_reg(idx: u32, val: u32) {\n let v = val & 0xffu;\n switch(idx) {\n case 0u: { cpu_b = v; }\n case 1u: { cpu_c = v; }\n case 2u: { cpu_d = v; }\n case 3u: { cpu_e = v; }\n case 4u: {\n if (idx_mode != 0u && idx_uses_mem == 0u) { set_idx_reg16((idx_reg16() & 0x00ffu) | (v << 8u)); }\n else { cpu_h = v; }\n }\n case 5u: {\n if (idx_mode != 0u && idx_uses_mem == 0u) { set_idx_reg16((idx_reg16() & 0xff00u) | v); }\n else { cpu_l = v; }\n }\n case 6u: { mem_write(idx_addr(), v); }\n case 7u: { cpu_a = v; }\n default: {}\n }\n}\n\nfn get_reg16(idx: u32) -> u32 {\n switch(idx) {\n case 0u: { return get_bc(); }\n case 1u: { return get_de(); }\n case 2u: { return idx_reg16(); } // HL / IX / IY\n case 3u: { return cpu_sp; }\n default: { return 0u; }\n }\n}\n\nfn set_reg16(idx: u32, val: u32) {\n let v = val & 0xffffu;\n switch(idx) {\n case 0u: { set_bc(v); }\n case 1u: { set_de(v); }\n case 2u: { set_idx_reg16(v); } // HL / IX / IY\n case 3u: { cpu_sp = v; }\n default: {}\n }\n}\n\nfn get_reg16_af(idx: u32) -> u32 {\n if (idx == 3u) { return get_af(); }\n return get_reg16(idx);\n}\n\nfn set_reg16_af(idx: u32, val: u32) {\n if (idx == 3u) { set_af(val); } else { set_reg16(idx, val); }\n}\n\n// === Index register (IX/IY) helpers ===\n// The 16-bit register the current prefix maps HL to (HL itself when no prefix).\nfn idx_reg16() -> u32 {\n if (idx_mode == 1u) { return cpu_ix; }\n if (idx_mode == 2u) { return cpu_iy; }\n return get_hl();\n}\nfn set_idx_reg16(v: u32) {\n if (idx_mode == 1u) { cpu_ix = v & 0xffffu; }\n else if (idx_mode == 2u) { cpu_iy = v & 0xffffu; }\n else { set_hl(v); }\n}\n// Address used for (HL) / (IX+d) / (IY+d).\nfn idx_addr() -> u32 {\n if (idx_mode != 0u) { return (idx_reg16() + idx_disp) & 0xffffu; }\n return get_hl();\n}\n// Sign-extend a displacement byte to 16 bits (two's complement).\nfn signext(b: u32) -> u32 {\n if (b >= 0x80u) { return b | 0xff00u; }\n return b;\n}\n// Does this main opcode dereference (HL)? (Determines displacement fetch and\n// whether H/L operands are IXH/IXL or real H/L under a DD/FD prefix.)\nfn op_uses_hl_mem(op: u32) -> bool {\n let x = (op >> 6u) & 3u;\n let y = (op >> 3u) & 7u;\n let z = op & 7u;\n if (x == 1u) { return (y == 6u || z == 6u) && !(y == 6u && z == 6u); } // LD r,(HL)/(HL),r (not HALT)\n if (x == 2u) { return z == 6u; } // ALU A,(HL)\n if (x == 0u) {\n if (z == 4u || z == 5u || z == 6u) { return y == 6u; } // INC/DEC (HL), LD (HL),n\n return false;\n }\n return false;\n}\n// Write a REAL 8-bit register (no IX/IY substitution) \u2014 used by the\n// undocumented DDCB/FDCB register-copy side effect.\nfn set_reg_raw(idx: u32, val: u32) {\n let v = val & 0xffu;\n switch(idx) {\n case 0u: { cpu_b = v; }\n case 1u: { cpu_c = v; }\n case 2u: { cpu_d = v; }\n case 3u: { cpu_e = v; }\n case 4u: { cpu_h = v; }\n case 5u: { cpu_l = v; }\n case 7u: { cpu_a = v; }\n default: {}\n }\n}\n\n// === Flag Helpers ===\nfn sz_flags(val: u32) -> u32 {\n var f = val & SFl;\n if (val == 0u) { f |= ZF; }\n f |= val & (F3 | F5);\n return f;\n}\n\nfn parity(val: u32) -> bool {\n var p = val;\n p ^= p >> 4u;\n p ^= p >> 2u;\n p ^= p >> 1u;\n return (p & 1u) == 0u;\n}\n\nfn check_cc(cc: u32) -> bool {\n switch(cc) {\n case 0u: { return (cpu_f & ZF) == 0u; }\n case 1u: { return (cpu_f & ZF) != 0u; }\n case 2u: { return (cpu_f & CF) == 0u; }\n case 3u: { return (cpu_f & CF) != 0u; }\n case 4u: { return (cpu_f & PF) == 0u; }\n case 5u: { return (cpu_f & PF) != 0u; }\n case 6u: { return (cpu_f & SFl) == 0u; }\n case 7u: { return (cpu_f & SFl) != 0u; }\n default: { return false; }\n }\n}\n\n// === ALU ===\nfn z80_alu(op: u32, val: u32) {\n let a = cpu_a;\n let c = cpu_f & CF;\n switch(op) {\n case 0u: { // ADD\n let r = a + val;\n cpu_f = sz_flags(r & 0xffu) | select(0u, CF, r > 0xffu) |\n ((a ^ val ^ r) & HF) |\n select(0u, PF, ((~(a ^ val)) & (a ^ r) & 0x80u) != 0u);\n cpu_a = r & 0xffu;\n }\n case 1u: { // ADC\n let r = a + val + c;\n cpu_f = sz_flags(r & 0xffu) | select(0u, CF, r > 0xffu) |\n ((a ^ val ^ r) & HF) |\n select(0u, PF, ((~(a ^ val)) & (a ^ r) & 0x80u) != 0u);\n cpu_a = r & 0xffu;\n }\n case 2u: { // SUB\n let r = i32(a) - i32(val);\n let ru = u32(r) & 0xffu;\n cpu_f = sz_flags(ru) | NF | select(0u, CF, r < 0) |\n ((a ^ val ^ u32(r)) & HF) |\n select(0u, PF, (((a ^ val) & (a ^ u32(r))) & 0x80u) != 0u);\n cpu_a = ru;\n }\n case 3u: { // SBC\n let r = i32(a) - i32(val) - i32(c);\n let ru = u32(r) & 0xffu;\n cpu_f = sz_flags(ru) | NF | select(0u, CF, r < 0) |\n ((a ^ val ^ u32(r)) & HF) |\n select(0u, PF, (((a ^ val) & (a ^ u32(r))) & 0x80u) != 0u);\n cpu_a = ru;\n }\n case 4u: { // AND\n cpu_a = a & val;\n cpu_f = sz_flags(cpu_a) | HF | select(0u, PF, parity(cpu_a));\n }\n case 5u: { // XOR\n cpu_a = a ^ val;\n cpu_f = sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));\n }\n case 6u: { // OR\n cpu_a = a | val;\n cpu_f = sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));\n }\n case 7u: { // CP\n let r = i32(a) - i32(val);\n let ru = u32(r) & 0xffu;\n cpu_f = (ru & SFl) | select(0u, ZF, ru == 0u) | (val & (F3 | F5)) | NF |\n select(0u, CF, r < 0) | ((a ^ val ^ u32(r)) & HF) |\n select(0u, PF, (((a ^ val) & (a ^ u32(r))) & 0x80u) != 0u);\n }\n default: {}\n }\n}\n\nfn z80_inc8(val: u32) -> u32 {\n let r = (val + 1u) & 0xffu;\n cpu_f = (cpu_f & CF) | sz_flags(r) |\n select(0u, PF, val == 0x7fu) |\n select(0u, HF, (r & 0x0fu) == 0u);\n return r;\n}\n\nfn z80_dec8(val: u32) -> u32 {\n let r = (val - 1u) & 0xffu;\n cpu_f = (cpu_f & CF) | sz_flags(r) | NF |\n select(0u, PF, val == 0x80u) |\n select(0u, HF, (val & 0x0fu) == 0u);\n return r;\n}\n\nfn z80_add_hl(val: u32) {\n let hl = idx_reg16(); // ADD HL,rp / ADD IX,rp / ADD IY,rp\n let r = hl + val;\n cpu_f = (cpu_f & (SFl | ZF | PF)) |\n select(0u, CF, r > 0xffffu) |\n select(0u, HF, ((hl ^ val ^ r) & 0x1000u) != 0u) |\n ((r >> 8u) & (F3 | F5));\n set_idx_reg16(r & 0xffffu);\n}\n\n// === Rotate/Shift for accumulator ===\nfn z80_rot_accum(y: u32) {\n let a = cpu_a;\n let c = cpu_f & CF;\n let keep = cpu_f & (SFl | ZF | PF);\n switch(y) {\n case 0u: { // RLCA\n cpu_a = ((a << 1u) | (a >> 7u)) & 0xffu;\n cpu_f = keep | (a >> 7u) | (cpu_a & (F3 | F5));\n }\n case 1u: { // RRCA\n cpu_a = ((a >> 1u) | (a << 7u)) & 0xffu;\n cpu_f = keep | (a & 1u) | (cpu_a & (F3 | F5));\n }\n case 2u: { // RLA\n cpu_a = ((a << 1u) | c) & 0xffu;\n cpu_f = keep | (a >> 7u) | (cpu_a & (F3 | F5));\n }\n case 3u: { // RRA\n cpu_a = ((a >> 1u) | (c << 7u)) & 0xffu;\n cpu_f = keep | (a & 1u) | (cpu_a & (F3 | F5));\n }\n case 4u: { // DAA\n var correction = 0u;\n var carry = c;\n if ((cpu_f & HF) != 0u || (a & 0x0fu) > 9u) { correction |= 0x06u; }\n if (c != 0u || a > 0x99u) { correction |= 0x60u; carry = 1u; }\n if ((cpu_f & NF) != 0u) { cpu_a = (a - correction) & 0xffu; }\n else { cpu_a = (a + correction) & 0xffu; }\n cpu_f = (cpu_f & NF) | sz_flags(cpu_a) | carry |\n ((a ^ cpu_a) & HF) | select(0u, PF, parity(cpu_a));\n }\n case 5u: { // CPL\n cpu_a = (~a) & 0xffu;\n cpu_f = (cpu_f & (SFl | ZF | PF | CF)) | HF | NF | (cpu_a & (F3 | F5));\n }\n case 6u: { // SCF\n cpu_f = (cpu_f & (SFl | ZF | PF)) | CF | (cpu_a & (F3 | F5));\n }\n case 7u: { // CCF\n cpu_f = (cpu_f & (SFl | ZF | PF)) |\n select(0u, HF, c != 0u) |\n select(CF, 0u, c != 0u) |\n (cpu_a & (F3 | F5));\n }\n default: {}\n }\n}\n\n// === CB Prefix (bit ops, rotates, shifts) ===\nfn z80_cb_rot(op: u32, val: u32) -> u32 {\n let c = cpu_f & CF;\n var r = 0u;\n switch(op) {\n case 0u: { r = ((val << 1u) | (val >> 7u)) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }\n case 1u: { r = ((val >> 1u) | (val << 7u)) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }\n case 2u: { r = ((val << 1u) | c) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }\n case 3u: { r = ((val >> 1u) | (c << 7u)) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }\n case 4u: { r = (val << 1u) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }\n case 5u: { r = ((val >> 1u) | (val & 0x80u)) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }\n case 6u: { r = ((val << 1u) | 1u) & 0xffu; cpu_f = sz_flags(r) | (val >> 7u) | select(0u, PF, parity(r)); }\n case 7u: { r = (val >> 1u) & 0xffu; cpu_f = sz_flags(r) | (val & 1u) | select(0u, PF, parity(r)); }\n default: { r = val; }\n }\n return r;\n}\n\nfn z80_exec_cb() {\n let op = z80_fetch();\n r_inc(); // M1: CB-page opcode fetch\n let x = (op >> 6u) & 3u;\n let y = (op >> 3u) & 7u;\n let z = op & 7u;\n let val = get_reg(z);\n switch(x) {\n case 0u: { set_reg(z, z80_cb_rot(y, val)); }\n case 1u: { // BIT\n cpu_f = (cpu_f & CF) | HF |\n select(0u, ZF | PF, (val & (1u << y)) == 0u) |\n select(0u, SFl, y == 7u && (val & 0x80u) != 0u) |\n (val & (F3 | F5));\n }\n case 2u: { set_reg(z, val & ~(1u << y)); }\n case 3u: { set_reg(z, val | (1u << y)); }\n default: {}\n }\n}\n\n// DDCB / FDCB: operates on (IX+d)/(IY+d). The displacement (idx_disp) has\n// already been fetched. For rot/shift/RES/SET the result is written to memory\n// AND (undocumented) copied to the real register in the low 3 bits unless it is\n// 6. For BIT, the undocumented F3/F5 come from the high byte of the address.\nfn z80_exec_idxcb(cbop: u32) {\n let addr = (idx_reg16() + idx_disp) & 0xffffu;\n let cx = (cbop >> 6u) & 3u;\n let cy = (cbop >> 3u) & 7u;\n let cz = cbop & 7u;\n let val = mem_read(addr);\n switch(cx) {\n case 0u: {\n let r = z80_cb_rot(cy, val);\n mem_write(addr, r);\n if (cz != 6u) { set_reg_raw(cz, r); }\n }\n case 1u: { // BIT n,(IX+d)\n cpu_f = (cpu_f & CF) | HF |\n select(0u, ZF | PF, (val & (1u << cy)) == 0u) |\n select(0u, SFl, cy == 7u && (val & 0x80u) != 0u) |\n ((addr >> 8u) & (F3 | F5));\n }\n case 2u: {\n let r = val & ~(1u << cy);\n mem_write(addr, r);\n if (cz != 6u) { set_reg_raw(cz, r); }\n }\n case 3u: {\n let r = val | (1u << cy);\n mem_write(addr, r);\n if (cz != 6u) { set_reg_raw(cz, r); }\n }\n default: {}\n }\n}\n\n// === Block Transfer (ED prefix) ===\nfn z80_ldi() {\n let val = mem_read(get_hl());\n mem_write(get_de(), val);\n set_hl((get_hl() + 1u) & 0xffffu);\n set_de((get_de() + 1u) & 0xffffu);\n set_bc((get_bc() - 1u) & 0xffffu);\n let n = (val + cpu_a) & 0xffu;\n cpu_f = (cpu_f & (SFl | ZF | CF)) |\n select(0u, PF, get_bc() != 0u) |\n (n & F3) | select(0u, F5, (n & 0x02u) != 0u);\n}\n\nfn z80_ldd() {\n let val = mem_read(get_hl());\n mem_write(get_de(), val);\n set_hl((get_hl() - 1u) & 0xffffu);\n set_de((get_de() - 1u) & 0xffffu);\n set_bc((get_bc() - 1u) & 0xffffu);\n let n = (val + cpu_a) & 0xffu;\n cpu_f = (cpu_f & (SFl | ZF | CF)) |\n select(0u, PF, get_bc() != 0u) |\n (n & F3) | select(0u, F5, (n & 0x02u) != 0u);\n}\n\nfn z80_cpi() {\n let val = mem_read(get_hl());\n let r = (cpu_a - val) & 0xffu;\n let hf = (cpu_a ^ val ^ r) & HF;\n // Undocumented F3/F5 come from n = A-(HL)-HF (bit 3 -> F3, bit 1 -> F5).\n let n = (r - select(0u, 1u, hf != 0u)) & 0xffu;\n set_hl((get_hl() + 1u) & 0xffffu);\n set_bc((get_bc() - 1u) & 0xffffu);\n cpu_f = (cpu_f & CF) | (r & SFl) | select(0u, ZF, r == 0u) | NF |\n hf | select(0u, PF, get_bc() != 0u) |\n (n & F3) | ((n & 0x02u) << 4u);\n}\n\nfn z80_cpd() {\n let val = mem_read(get_hl());\n let r = (cpu_a - val) & 0xffu;\n let hf = (cpu_a ^ val ^ r) & HF;\n let n = (r - select(0u, 1u, hf != 0u)) & 0xffu;\n set_hl((get_hl() - 1u) & 0xffffu);\n set_bc((get_bc() - 1u) & 0xffffu);\n cpu_f = (cpu_f & CF) | (r & SFl) | select(0u, ZF, r == 0u) | NF |\n hf | select(0u, PF, get_bc() != 0u) |\n (n & F3) | ((n & 0x02u) << 4u);\n}\n\n// === ED Prefix ===\nfn z80_exec_ed() {\n let op = z80_fetch();\n r_inc(); // M1: ED-page opcode fetch\n switch(op) {\n case 0xa0u: { z80_ldi(); }\n case 0xa8u: { z80_ldd(); }\n case 0xb0u: { z80_ldi(); if (get_bc() != 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } } // LDIR\n case 0xb8u: { z80_ldd(); if (get_bc() != 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } } // LDDR\n case 0xa1u: { z80_cpi(); }\n case 0xa9u: { z80_cpd(); }\n case 0xb1u: { z80_cpi(); if (get_bc() != 0u && (cpu_f & ZF) == 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } }\n case 0xb9u: { z80_cpd(); if (get_bc() != 0u && (cpu_f & ZF) == 0u) { cpu_pc = (cpu_pc - 2u) & 0xffffu; } }\n // NEG\n case 0x44u, 0x4cu, 0x54u, 0x5cu, 0x64u, 0x6cu, 0x74u, 0x7cu: {\n let a = cpu_a; cpu_a = 0u; z80_alu(2u, a);\n }\n // RETN/RETI\n case 0x45u, 0x4du, 0x55u, 0x5du, 0x65u, 0x6du, 0x75u, 0x7du: {\n cpu_iff1 = cpu_iff2; cpu_pc = z80_pop16();\n }\n // LD I,A / LD R,A / LD A,I / LD A,R\n case 0x47u: { cpu_i = cpu_a; } // LD I,A\n case 0x4fu: { cpu_r = cpu_a; } // LD R,A\n // LD A,I / LD A,R: load I or R into A. Flags: S/Z (+F3/F5) from the loaded\n // value, PF = IFF2, N/H reset, C preserved.\n case 0x57u: { cpu_a = cpu_i; cpu_f = (cpu_f & CF) | sz_flags(cpu_i) | select(0u, PF, cpu_iff2 != 0u); }\n case 0x5fu: { cpu_a = cpu_r; cpu_f = (cpu_f & CF) | sz_flags(cpu_r) | select(0u, PF, cpu_iff2 != 0u); }\n // LD (nn), rr\n case 0x43u, 0x53u, 0x63u, 0x73u: {\n let nn = z80_fetch_word();\n let rp = (op >> 4u) & 3u;\n let val = get_reg16(rp);\n mem_write(nn, val & 0xffu);\n mem_write((nn + 1u) & 0xffffu, (val >> 8u) & 0xffu);\n }\n // LD rr, (nn)\n case 0x4bu, 0x5bu, 0x6bu, 0x7bu: {\n let nn = z80_fetch_word();\n let rp = (op >> 4u) & 3u;\n let lo = mem_read(nn);\n let hi = mem_read((nn + 1u) & 0xffffu);\n set_reg16(rp, (hi << 8u) | lo);\n }\n // ADC HL, rr\n case 0x4au, 0x5au, 0x6au, 0x7au: {\n let rp = (op >> 4u) & 3u;\n let hl = get_hl();\n let val = get_reg16(rp);\n let c = cpu_f & CF;\n let r = hl + val + c;\n cpu_f = ((r >> 8u) & SFl) | select(0u, ZF, (r & 0xffffu) == 0u) |\n select(0u, HF, ((hl ^ val ^ r) & 0x1000u) != 0u) |\n select(0u, PF, ((~(hl ^ val)) & (hl ^ r) & 0x8000u) != 0u) |\n select(0u, CF, r > 0xffffu) | ((r >> 8u) & (F3 | F5));\n set_hl(r & 0xffffu);\n }\n // SBC HL, rr\n case 0x42u, 0x52u, 0x62u, 0x72u: {\n let rp = (op >> 4u) & 3u;\n let hl = get_hl();\n let val = get_reg16(rp);\n let c = cpu_f & CF;\n let r = i32(hl) - i32(val) - i32(c);\n let ru = u32(r) & 0xffffu;\n cpu_f = ((ru >> 8u) & SFl) | select(0u, ZF, ru == 0u) | NF |\n select(0u, HF, ((hl ^ val ^ u32(r)) & 0x1000u) != 0u) |\n select(0u, PF, (((hl ^ val) & (hl ^ u32(r))) & 0x8000u) != 0u) |\n select(0u, CF, r < 0) | ((ru >> 8u) & (F3 | F5));\n set_hl(ru);\n }\n // RRD\n case 0x67u: {\n let m = mem_read(get_hl());\n mem_write(get_hl(), ((cpu_a << 4u) | (m >> 4u)) & 0xffu);\n cpu_a = (cpu_a & 0xf0u) | (m & 0x0fu);\n cpu_f = (cpu_f & CF) | sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));\n }\n // RLD\n case 0x6fu: {\n let m = mem_read(get_hl());\n mem_write(get_hl(), ((m << 4u) | (cpu_a & 0x0fu)) & 0xffu);\n cpu_a = (cpu_a & 0xf0u) | (m >> 4u);\n cpu_f = (cpu_f & CF) | sz_flags(cpu_a) | select(0u, PF, parity(cpu_a));\n }\n // IN r,(C) - simplified, just set to 0\n case 0x40u, 0x48u, 0x50u, 0x58u, 0x60u, 0x68u, 0x70u, 0x78u: {\n set_reg((op >> 3u) & 7u, 0u);\n }\n default: {} // unknown ED ops = NOP\n }\n}\n\n// === Main Opcode Execution ===\nfn z80_exec_x0(y: u32, z: u32, p: u32, q: u32) {\n switch(z) {\n case 0u: {\n switch(y) {\n case 0u: {} // NOP\n case 1u: { // EX AF,AF'\n var t = cpu_a; cpu_a = cpu_a2; cpu_a2 = t;\n t = cpu_f; cpu_f = cpu_f2; cpu_f2 = t;\n }\n case 2u: { // DJNZ\n let d = signed_byte(z80_fetch());\n cpu_b = (cpu_b - 1u) & 0xffu;\n if (cpu_b != 0u) { cpu_pc = u32(i32(cpu_pc) + d) & 0xffffu; }\n }\n case 3u: { // JR\n let d = signed_byte(z80_fetch());\n cpu_pc = u32(i32(cpu_pc) + d) & 0xffffu;\n }\n default: { // JR cc (y-4)\n let d = signed_byte(z80_fetch());\n if (check_cc(y - 4u)) { cpu_pc = u32(i32(cpu_pc) + d) & 0xffffu; }\n }\n }\n }\n case 1u: {\n if (q == 0u) { set_reg16(p, z80_fetch_word()); }\n else { z80_add_hl(get_reg16(p)); }\n }\n case 2u: {\n if (q == 0u) {\n switch(p) {\n case 0u: { mem_write(get_bc(), cpu_a); }\n case 1u: { mem_write(get_de(), cpu_a); }\n case 2u: { let nn = z80_fetch_word(); let hl = idx_reg16(); mem_write(nn, hl & 0xffu); mem_write((nn+1u) & 0xffffu, (hl >> 8u) & 0xffu); }\n case 3u: { mem_write(z80_fetch_word(), cpu_a); }\n default: {}\n }\n } else {\n switch(p) {\n case 0u: { cpu_a = mem_read(get_bc()); }\n case 1u: { cpu_a = mem_read(get_de()); }\n case 2u: { let nn = z80_fetch_word(); let lo = mem_read(nn); let hi = mem_read((nn+1u) & 0xffffu); set_idx_reg16((hi << 8u) | lo); }\n case 3u: { cpu_a = mem_read(z80_fetch_word()); }\n default: {}\n }\n }\n }\n case 3u: {\n if (q == 0u) { set_reg16(p, (get_reg16(p) + 1u) & 0xffffu); }\n else { set_reg16(p, (get_reg16(p) - 1u) & 0xffffu); }\n }\n case 4u: { set_reg(y, z80_inc8(get_reg(y))); }\n case 5u: { set_reg(y, z80_dec8(get_reg(y))); }\n case 6u: { set_reg(y, z80_fetch()); }\n case 7u: { z80_rot_accum(y); }\n default: {}\n }\n}\n\nfn z80_exec_x3(y: u32, z: u32, p: u32, q: u32) {\n switch(z) {\n case 0u: { if (check_cc(y)) { cpu_pc = z80_pop16(); } }\n case 1u: {\n if (q == 0u) { set_reg16_af(p, z80_pop16()); }\n else {\n switch(p) {\n case 0u: { cpu_pc = z80_pop16(); } // RET\n case 1u: { // EXX\n var t = cpu_b; cpu_b = cpu_b2; cpu_b2 = t;\n t = cpu_c; cpu_c = cpu_c2; cpu_c2 = t;\n t = cpu_d; cpu_d = cpu_d2; cpu_d2 = t;\n t = cpu_e; cpu_e = cpu_e2; cpu_e2 = t;\n t = cpu_h; cpu_h = cpu_h2; cpu_h2 = t;\n t = cpu_l; cpu_l = cpu_l2; cpu_l2 = t;\n }\n case 2u: { cpu_pc = idx_reg16(); } // JP (HL)/(IX)/(IY)\n case 3u: { cpu_sp = idx_reg16(); } // LD SP,HL/IX/IY\n default: {}\n }\n }\n }\n case 2u: { let nn = z80_fetch_word(); if (check_cc(y)) { cpu_pc = nn; } }\n case 3u: {\n switch(y) {\n case 0u: { cpu_pc = z80_fetch_word(); } // JP nn\n case 1u: { z80_exec_cb(); }\n case 2u: { z80_fetch(); } // OUT (n),A - no I/O device, discard (matches zff outPort no-op)\n case 3u: { z80_fetch(); cpu_a = 0u; } // IN A,(n) - no I/O device, reads 0 (matches zff inPort\u21920)\n case 4u: { // EX (SP),HL / EX (SP),IX / EX (SP),IY\n let lo = mem_read(cpu_sp);\n let hi = mem_read((cpu_sp + 1u) & 0xffffu);\n let hl = idx_reg16();\n mem_write(cpu_sp, hl & 0xffu);\n mem_write((cpu_sp + 1u) & 0xffffu, (hl >> 8u) & 0xffu);\n set_idx_reg16((hi << 8u) | lo);\n }\n case 5u: { // EX DE,HL\n let td = cpu_d; let te = cpu_e;\n cpu_d = cpu_h; cpu_e = cpu_l;\n cpu_h = td; cpu_l = te;\n }\n case 6u: { cpu_iff1 = 0u; cpu_iff2 = 0u; }\n case 7u: { cpu_iff1 = 1u; cpu_iff2 = 1u; }\n default: {}\n }\n }\n case 4u: { let nn = z80_fetch_word(); if (check_cc(y)) { z80_push16(cpu_pc); cpu_pc = nn; } }\n case 5u: {\n if (q == 0u) { z80_push16(get_reg16_af(p)); }\n else {\n switch(p) {\n case 0u: { let nn = z80_fetch_word(); z80_push16(cpu_pc); cpu_pc = nn; } // CALL nn\n case 1u, 3u: {} // DD/FD prefix handled in z80_step\n case 2u: { z80_exec_ed(); }\n default: {}\n }\n }\n }\n case 6u: { z80_alu(y, z80_fetch()); }\n case 7u: { z80_push16(cpu_pc); cpu_pc = y * 8u; } // RST\n default: {}\n }\n}\n\nfn z80_execute(op: u32) {\n let x = (op >> 6u) & 3u;\n let y = (op >> 3u) & 7u;\n let z = op & 7u;\n let p = (y >> 1u) & 3u;\n let q = y & 1u;\n switch(x) {\n case 0u: { z80_exec_x0(y, z, p, q); }\n case 1u: {\n if (y == 6u && z == 6u) {\n // HALT: mark halted and back PC up onto the HALT opcode so the\n // CPU re-executes it every step until an interrupt.\n cpu_halted = 1u;\n cpu_pc = (cpu_pc - 1u) & 0xffffu;\n }\n else { set_reg(y, get_reg(z)); }\n }\n case 2u: { z80_alu(y, get_reg(z)); }\n case 3u: { z80_exec_x3(y, z, p, q); }\n default: {}\n }\n}\n\nfn z80_step() {\n // While halted, the CPU keeps executing HALT: each step is an M1 fetch of\n // the same opcode (R increments), PC stays put. It resumes only on interrupt.\n if (cpu_halted != 0u) { r_inc(); return; }\n // Reset per-instruction index-prefix state.\n idx_mode = 0u;\n idx_uses_mem = 0u;\n idx_disp = 0u;\n\n var op = z80_fetch();\n r_inc(); // M1: opcode (or prefix) fetch\n // A DD/FD prefix selects IX/IY for the following opcode.\n if (op == 0xddu || op == 0xfdu) {\n idx_mode = select(2u, 1u, op == 0xddu);\n let next = z80_fetch();\n r_inc(); // M1: opcode after the prefix\n // A prefix immediately followed by another prefix or ED is a wasted M1:\n // this step consumes just the prefix; back up so the next step restarts\n // at the following byte (matches real Z80 timing and avoids an\n // unbounded fetch loop on all-prefix programs).\n if (next == 0xddu || next == 0xfdu || next == 0xedu) {\n cpu_pc = (cpu_pc - 1u) & 0xffffu;\n return;\n }\n op = next;\n }\n\n // Host hook: skip execution (treat as NOP) if requested.\n if (on_fetch_opcode(op)) { return; }\n\n if (idx_mode != 0u) {\n if (op == 0xcbu) {\n // DDCB/FDCB: displacement precedes the CB opcode.\n idx_disp = signext(z80_fetch());\n let cbop = z80_fetch();\n z80_exec_idxcb(cbop);\n return;\n }\n if (op_uses_hl_mem(op)) {\n idx_uses_mem = 1u;\n idx_disp = signext(z80_fetch());\n }\n }\n z80_execute(op);\n}\n";
2
2
  //# sourceMappingURL=z80-core.wgsl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"z80-core.wgsl.d.ts","sourceRoot":"","sources":["../src/z80-core.wgsl.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa,634BA+wBzB,CAAC"}
1
+ {"version":3,"file":"z80-core.wgsl.d.ts","sourceRoot":"","sources":["../src/z80-core.wgsl.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa,gv6BA+xBzB,CAAC"}
@@ -28,6 +28,11 @@ var<private> cpu_iff1: u32;
28
28
  var<private> cpu_iff2: u32;
29
29
  var<private> cpu_ix: u32;
30
30
  var<private> cpu_iy: u32;
31
+ var<private> cpu_i: u32; // interrupt vector register (I)
32
+ var<private> cpu_r: u32; // memory refresh register (R); bits 0-6 count M1 cycles, bit 7 preserved
33
+
34
+ // Increment R once per M1 (opcode/prefix) fetch: 7-bit counter, bit 7 preserved.
35
+ fn r_inc() { cpu_r = (cpu_r & 0x80u) | ((cpu_r + 1u) & 0x7fu); }
31
36
  // Index-prefix state for the instruction currently executing:
32
37
  // idx_mode: 0 = HL, 1 = IX, 2 = IY
33
38
  // idx_disp: sign-extended displacement for (IX+d)/(IY+d)
@@ -406,6 +411,7 @@ fn z80_cb_rot(op: u32, val: u32) -> u32 {
406
411
 
407
412
  fn z80_exec_cb() {
408
413
  let op = z80_fetch();
414
+ r_inc(); // M1: CB-page opcode fetch
409
415
  let x = (op >> 6u) & 3u;
410
416
  let y = (op >> 3u) & 7u;
411
417
  let z = op & 7u;
@@ -513,6 +519,7 @@ fn z80_cpd() {
513
519
  // === ED Prefix ===
514
520
  fn z80_exec_ed() {
515
521
  let op = z80_fetch();
522
+ r_inc(); // M1: ED-page opcode fetch
516
523
  switch(op) {
517
524
  case 0xa0u: { z80_ldi(); }
518
525
  case 0xa8u: { z80_ldd(); }
@@ -531,12 +538,12 @@ fn z80_exec_ed() {
531
538
  cpu_iff1 = cpu_iff2; cpu_pc = z80_pop16();
532
539
  }
533
540
  // LD I,A / LD R,A / LD A,I / LD A,R
534
- case 0x47u: {} // LD I,A - no I register in our sim
535
- case 0x4fu: {} // LD R,A
536
- // LD A,I / LD A,R: I and R are not modelled (treated as 0), so A becomes 0.
537
- // Flags: S/Z from the loaded value, PF = IFF2, N/H reset, C preserved.
538
- case 0x57u: { cpu_a = 0u; cpu_f = (cpu_f & CF) | sz_flags(0u) | select(0u, PF, cpu_iff2 != 0u); }
539
- case 0x5fu: { cpu_a = 0u; cpu_f = (cpu_f & CF) | sz_flags(0u) | select(0u, PF, cpu_iff2 != 0u); }
541
+ case 0x47u: { cpu_i = cpu_a; } // LD I,A
542
+ case 0x4fu: { cpu_r = cpu_a; } // LD R,A
543
+ // LD A,I / LD A,R: load I or R into A. Flags: S/Z (+F3/F5) from the loaded
544
+ // value, PF = IFF2, N/H reset, C preserved.
545
+ case 0x57u: { cpu_a = cpu_i; cpu_f = (cpu_f & CF) | sz_flags(cpu_i) | select(0u, PF, cpu_iff2 != 0u); }
546
+ case 0x5fu: { cpu_a = cpu_r; cpu_f = (cpu_f & CF) | sz_flags(cpu_r) | select(0u, PF, cpu_iff2 != 0u); }
540
547
  // LD (nn), rr
541
548
  case 0x43u, 0x53u, 0x63u, 0x73u: {
542
549
  let nn = z80_fetch_word();
@@ -736,7 +743,12 @@ fn z80_execute(op: u32) {
736
743
  switch(x) {
737
744
  case 0u: { z80_exec_x0(y, z, p, q); }
738
745
  case 1u: {
739
- if (y == 6u && z == 6u) { cpu_halted = 1u; }
746
+ if (y == 6u && z == 6u) {
747
+ // HALT: mark halted and back PC up onto the HALT opcode so the
748
+ // CPU re-executes it every step until an interrupt.
749
+ cpu_halted = 1u;
750
+ cpu_pc = (cpu_pc - 1u) & 0xffffu;
751
+ }
740
752
  else { set_reg(y, get_reg(z)); }
741
753
  }
742
754
  case 2u: { z80_alu(y, get_reg(z)); }
@@ -746,17 +758,21 @@ fn z80_execute(op: u32) {
746
758
  }
747
759
 
748
760
  fn z80_step() {
749
- if (cpu_halted != 0u) { return; }
761
+ // While halted, the CPU keeps executing HALT: each step is an M1 fetch of
762
+ // the same opcode (R increments), PC stays put. It resumes only on interrupt.
763
+ if (cpu_halted != 0u) { r_inc(); return; }
750
764
  // Reset per-instruction index-prefix state.
751
765
  idx_mode = 0u;
752
766
  idx_uses_mem = 0u;
753
767
  idx_disp = 0u;
754
768
 
755
769
  var op = z80_fetch();
770
+ r_inc(); // M1: opcode (or prefix) fetch
756
771
  // A DD/FD prefix selects IX/IY for the following opcode.
757
772
  if (op == 0xddu || op == 0xfdu) {
758
773
  idx_mode = select(2u, 1u, op == 0xddu);
759
774
  let next = z80_fetch();
775
+ r_inc(); // M1: opcode after the prefix
760
776
  // A prefix immediately followed by another prefix or ED is a wasted M1:
761
777
  // this step consumes just the prefix; back up so the next step restarts
762
778
  // at the following byte (matches real Z80 timing and avoids an
@@ -1 +1 @@
1
- {"version":3,"file":"z80-core.wgsl.js","sourceRoot":"","sources":["../src/z80-core.wgsl.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,+DAA+D;AAC/D,EAAE;AACF,+EAA+E;AAC/E,kEAAkE;AAElE,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+wBvC,CAAC"}
1
+ {"version":3,"file":"z80-core.wgsl.js","sourceRoot":"","sources":["../src/z80-core.wgsl.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,+DAA+D;AAC/D,EAAE;AACF,+EAA+E;AAC/E,kEAAkE;AAElE,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+xBvC,CAAC"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@neovand/zilion",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "description": "A zillion Z80s in parallel on your GPU — a WebGPU-accelerated, differentially-tested Z80 CPU emulator for batch execution, artificial life, and genetic programming.",
7
+ "description": "A zillion Z80s in parallel on your GPU \u2014 a WebGPU-accelerated, differentially-tested Z80 CPU emulator for batch execution, artificial life, and genetic programming.",
8
8
  "type": "module",
9
9
  "main": "./dist/index.js",
10
10
  "module": "./dist/index.js",