@fugloy/emu 0.0.0-stage → 0.2.0

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.
@@ -0,0 +1,3343 @@
1
+ /**
2
+ * Z80 CPU Emulator Core
3
+ * Implements COMPLETE Z80 instruction set with cycle-accurate timing
4
+ *
5
+ * This is a FULL Z80 implementation matching the real TRS-80 Model III.
6
+ * ALL instructions must be implemented:
7
+ * - Base opcodes: 0x00-0xFF (252 instructions)
8
+ * - CB prefix: 0xCB00-0xCBFF (bit operations, rotates, shifts)
9
+ * - ED prefix: 0xED00-0xEDFF (extended instructions, block operations)
10
+ * - DD prefix: 0xDD00-0xDDFF (IX register operations)
11
+ * - FD prefix: 0xFD00-0xFDFF (IY register operations)
12
+ *
13
+ * Reference: docs/cschweda-z80-assembler-8a5edab282632443.txt (opcodes.js)
14
+ */
15
+
16
+ // Flag bit positions
17
+ const FLAG_C = 0; // Carry
18
+ const FLAG_N = 1; // Add/Subtract
19
+ const FLAG_PV = 2; // Parity/Overflow
20
+ const FLAG_H = 4; // Half Carry
21
+ const FLAG_Z = 6; // Zero
22
+ const FLAG_S = 7; // Sign
23
+
24
+ // The 8-bit registers auto-mask writes to 8 bits via accessor properties
25
+ // over a closure-private backing store. Accessors, not a Proxy: register
26
+ // access is the hottest path in the emulator, and V8 inline-caches plain
27
+ // accessors where every Proxy trap costs a handler call plus a key
28
+ // lookup — swapping the Proxy for accessors measured 3.25x faster
29
+ // overall emulation. The backing store is only reachable through the
30
+ // setters, so its values are always pre-masked and reads need no mask.
31
+ const EIGHT_BIT_REGS = [
32
+ "A", "F", "B", "C", "D", "E", "H", "L",
33
+ "A_", "F_", "B_", "C_", "D_", "E_", "H_", "L_",
34
+ "IXH", "IXL", "IYH", "IYL",
35
+ "I", "R",
36
+ ];
37
+
38
+ // DD/FD CB register field (low 3 bits) -> destination for the
39
+ // undocumented result copy; index 6 is the documented memory-only form.
40
+ const INDEXED_CB_COPY_REGS = ["B", "C", "D", "E", "H", "L", null, "A"];
41
+
42
+ function makeRegisterFile() {
43
+ const raw = {};
44
+ for (const name of EIGHT_BIT_REGS) {
45
+ raw[name] = 0x00;
46
+ }
47
+ // SP/PC stay plain 16-bit data properties; every write site masks them.
48
+ const registers = {
49
+ SP: 0xffff,
50
+ PC: 0x0000,
51
+ };
52
+ for (const name of EIGHT_BIT_REGS) {
53
+ Object.defineProperty(registers, name, {
54
+ enumerable: true,
55
+ get() {
56
+ return raw[name];
57
+ },
58
+ set(value) {
59
+ raw[name] = value & 0xff;
60
+ },
61
+ });
62
+ }
63
+ return registers;
64
+ }
65
+
66
+ export class Z80CPU {
67
+ constructor() {
68
+ this.registers = makeRegisterFile();
69
+
70
+ // Interrupt system
71
+ this.IFF1 = false;
72
+ this.IFF2 = false;
73
+ this.interruptMode = 0;
74
+ this.halted = false;
75
+ // EI enables interrupts only after the instruction that follows it;
76
+ // counts down at the end of each executed instruction (2 -> 1 -> enable).
77
+ this.pendingEI = 0;
78
+ this.afterPrefix = false; // a DD or FD the next byte made a no-op; no maskable interrupt until it runs
79
+
80
+ // When true, unimplemented opcodes throw instead of executing as NOP.
81
+ // Test harnesses use this so instruction-set gaps can't hide.
82
+ this.strictMode = false;
83
+ // Where warnings go (unimplemented opcodes); a no-op unless the host sets one.
84
+ this.log = () => {};
85
+
86
+ // Execution state
87
+ this.cycles = 0;
88
+ this.lastOpCycles = 0;
89
+ this.wz = 0x0000; // MEMPTR (internal address latch)
90
+
91
+ // External interfaces (connected by system)
92
+ this.readMemory = (address) => 0x00;
93
+ this.writeMemory = (address, value) => {};
94
+ this.readPort = (port) => 0x00;
95
+ this.writePort = (port, value) => {};
96
+
97
+ // Unimplemented opcode tracking (for debugging)
98
+ this.unimplementedOpcodes = new Set();
99
+
100
+ // Setup opcode handlers
101
+ this.setupOpcodeHandlers();
102
+ }
103
+
104
+ // Register pair getters/setters
105
+ getRegisterPair(high, low) {
106
+ return (this.registers[high] << 8) | this.registers[low];
107
+ }
108
+
109
+ setRegisterPair(high, low, value) {
110
+ this.registers[high] = (value >> 8) & 0xff;
111
+ this.registers[low] = value & 0xff;
112
+ }
113
+
114
+ // Ensure 8-bit registers wrap correctly
115
+ setRegister(reg, value) {
116
+ this.registers[reg] = value & 0xff;
117
+ }
118
+
119
+ get BC() {
120
+ return this.getRegisterPair("B", "C");
121
+ }
122
+ set BC(value) {
123
+ this.setRegisterPair("B", "C", value);
124
+ }
125
+
126
+ get DE() {
127
+ return this.getRegisterPair("D", "E");
128
+ }
129
+ set DE(value) {
130
+ this.setRegisterPair("D", "E", value);
131
+ }
132
+
133
+ get HL() {
134
+ return this.getRegisterPair("H", "L");
135
+ }
136
+ set HL(value) {
137
+ this.setRegisterPair("H", "L", value);
138
+ }
139
+
140
+ get IX() {
141
+ return this.getRegisterPair("IXH", "IXL");
142
+ }
143
+ set IX(value) {
144
+ this.setRegisterPair("IXH", "IXL", value);
145
+ }
146
+
147
+ get IY() {
148
+ return this.getRegisterPair("IYH", "IYL");
149
+ }
150
+ set IY(value) {
151
+ this.setRegisterPair("IYH", "IYL", value);
152
+ }
153
+
154
+ // Flag operations
155
+ getFlag(flag) {
156
+ return (this.registers.F >> flag) & 1;
157
+ }
158
+
159
+ /**
160
+ * Undocumented X/Y flags (F bits 3 and 5): every flag-writing
161
+ * instruction copies bits 3/5 of some instruction-specific value —
162
+ * usually the result, but CP uses its operand, BIT n,(HL) uses
163
+ * MEMPTR's high byte, and the block ops use a synthetic n (see the
164
+ * call sites). ZEXALL-class fidelity lives or dies here.
165
+ */
166
+ _xy(value) {
167
+ this.registers.F = (this.registers.F & ~0x28) | (value & 0x28);
168
+ }
169
+
170
+ /** Block-op X/Y mapping: Y = bit 1 of n, X = bit 3 of n. */
171
+ _xyBlock(n) {
172
+ this.registers.F =
173
+ (this.registers.F & ~0x28) | ((n & 0x02) << 4) | (n & 0x08);
174
+ }
175
+
176
+ setFlag(flag, value) {
177
+ if (value) {
178
+ this.registers.F |= 1 << flag;
179
+ } else {
180
+ this.registers.F &= ~(1 << flag);
181
+ }
182
+ }
183
+
184
+ get flagC() {
185
+ return this.getFlag(FLAG_C);
186
+ }
187
+ set flagC(v) {
188
+ this.setFlag(FLAG_C, v);
189
+ }
190
+
191
+ get flagN() {
192
+ return this.getFlag(FLAG_N);
193
+ }
194
+ set flagN(v) {
195
+ this.setFlag(FLAG_N, v);
196
+ }
197
+
198
+ get flagPV() {
199
+ return this.getFlag(FLAG_PV);
200
+ }
201
+ set flagPV(v) {
202
+ this.setFlag(FLAG_PV, v);
203
+ }
204
+
205
+ get flagH() {
206
+ return this.getFlag(FLAG_H);
207
+ }
208
+ set flagH(v) {
209
+ this.setFlag(FLAG_H, v);
210
+ }
211
+
212
+ get flagZ() {
213
+ return this.getFlag(FLAG_Z);
214
+ }
215
+ set flagZ(v) {
216
+ this.setFlag(FLAG_Z, v);
217
+ }
218
+
219
+ get flagS() {
220
+ return this.getFlag(FLAG_S);
221
+ }
222
+ set flagS(v) {
223
+ this.setFlag(FLAG_S, v);
224
+ }
225
+
226
+ // Reset CPU
227
+ reset() {
228
+ this.registers.PC = 0x0000;
229
+ this.registers.SP = 0xffff;
230
+ this.registers.I = 0x00;
231
+ this.registers.R = 0x00;
232
+ this.wz = 0x0000; // MEMPTR — feeds BIT n,(HL)'s X/Y flags
233
+ this.halted = false;
234
+ this.IFF1 = false;
235
+ this.IFF2 = false;
236
+ this.interruptMode = 0;
237
+ this.pendingEI = 0;
238
+ this.afterPrefix = false; // a DD or FD the next byte made a no-op; no maskable interrupt until it runs
239
+ this.cycles = 0;
240
+ }
241
+
242
+ // Execute single instruction
243
+ executeInstruction() {
244
+ this.afterPrefix = false;
245
+ if (this.halted) {
246
+ this.lastOpCycles = 4;
247
+ this.cycles += 4;
248
+ return 4;
249
+ }
250
+
251
+ const opcode = this.readMemory(this.registers.PC);
252
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
253
+
254
+ this.incrementR();
255
+
256
+ const cycles = this.decodeAndExecute(opcode);
257
+
258
+ // Promote a pending EI once the instruction after EI has completed
259
+ if (this.pendingEI > 0) {
260
+ this.pendingEI--;
261
+ if (this.pendingEI === 0) {
262
+ this.IFF1 = true;
263
+ this.IFF2 = true;
264
+ }
265
+ }
266
+
267
+ this.lastOpCycles = cycles;
268
+ this.cycles += cycles;
269
+
270
+ return cycles;
271
+ }
272
+
273
+ // R increments once per opcode fetch (so twice for prefixed
274
+ // instructions); bit 7 is only ever set by LD R,A and must be preserved.
275
+ incrementR() {
276
+ this.registers.R =
277
+ (this.registers.R & 0x80) | ((this.registers.R + 1) & 0x7f);
278
+ }
279
+
280
+ /**
281
+ * Accept a maskable interrupt (the Model III raises these at 30 Hz).
282
+ * Returns true if the interrupt was taken. In IM 2 the dataBus byte
283
+ * selects the vector-table entry; the Model III bus presents 0xFF,
284
+ * which in IM 0/1 behaves as RST 38.
285
+ */
286
+ interrupt(dataBus = 0xff) {
287
+ if (!this.IFF1 || this.pendingEI > 0 || this.afterPrefix) {
288
+ return false;
289
+ }
290
+
291
+ this.halted = false;
292
+ this.IFF1 = false;
293
+ this.IFF2 = false;
294
+ this.incrementR(); // acknowledge is an M1 cycle
295
+
296
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
297
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
298
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
299
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
300
+
301
+ if (this.interruptMode === 2) {
302
+ const vecAddr = ((this.registers.I << 8) | (dataBus & 0xfe)) & 0xffff;
303
+ const low = this.readMemory(vecAddr);
304
+ const high = this.readMemory((vecAddr + 1) & 0xffff);
305
+ this.registers.PC = (high << 8) | low;
306
+ this.cycles += 19;
307
+ } else {
308
+ // IM 0 with 0xFF on the bus is RST 38 — identical to IM 1 here
309
+ this.registers.PC = 0x0038;
310
+ this.cycles += 13;
311
+ }
312
+ this.wz = this.registers.PC; // MEMPTR, as for CALL
313
+
314
+ return true;
315
+ }
316
+
317
+ /**
318
+ * Non-maskable interrupt: always taken, saves IFF1 into IFF2 so RETN
319
+ * can restore it, jumps to 0x0066.
320
+ */
321
+ nmi() {
322
+ this.halted = false;
323
+ this.IFF2 = this.IFF1;
324
+ this.IFF1 = false;
325
+ this.incrementR(); // acknowledge is an M1 cycle
326
+
327
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
328
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
329
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
330
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
331
+
332
+ this.registers.PC = 0x0066;
333
+ this.wz = 0x0066; // MEMPTR, as for CALL
334
+ this.cycles += 11;
335
+ }
336
+
337
+ // Decode and execute opcode
338
+ decodeAndExecute(opcode) {
339
+ // Extended instructions (second opcode fetch bumps R again)
340
+ if (opcode === 0xcb) {
341
+ this.incrementR();
342
+ return this.executeCB();
343
+ }
344
+ if (opcode === 0xed) {
345
+ this.incrementR();
346
+ return this.executeED();
347
+ }
348
+ if (opcode === 0xdd) {
349
+ return this.executeDD();
350
+ }
351
+ if (opcode === 0xfd) {
352
+ return this.executeFD();
353
+ }
354
+
355
+ // Standard instructions
356
+ const handler = this.opcodeHandlers[opcode];
357
+ if (handler) {
358
+ return handler.call(this);
359
+ }
360
+
361
+ return this.reportUnimplemented(opcode, "");
362
+ }
363
+
364
+ // Shared unimplemented-opcode path: throw in strict mode so gaps
365
+ // surface in tests; warn once and NOP otherwise (browser behavior).
366
+ reportUnimplemented(opcode, prefix) {
367
+ const label = `${prefix}0x${opcode
368
+ .toString(16)
369
+ .toUpperCase()
370
+ .padStart(2, "0")}`;
371
+ if (this.strictMode) {
372
+ throw new Error(
373
+ `Unimplemented opcode ${label} at PC=0x${(this.registers.PC - 1)
374
+ .toString(16)
375
+ .padStart(4, "0")}`
376
+ );
377
+ }
378
+ const key = prefix ? `${prefix}${opcode}` : opcode;
379
+ if (!this.unimplementedOpcodes.has(key)) {
380
+ this.unimplementedOpcodes.add(key);
381
+ this.log("warn",
382
+ `Unimplemented opcode: ${label} at PC=0x${(this.registers.PC - 1)
383
+ .toString(16)
384
+ .padStart(4, "0")}`
385
+ );
386
+ }
387
+ return 4;
388
+ }
389
+
390
+ // Setup opcode handlers
391
+ setupOpcodeHandlers() {
392
+ this.opcodeHandlers = {};
393
+
394
+ // NOP
395
+ this.opcodeHandlers[0x00] = () => 4;
396
+
397
+ // HALT
398
+ this.opcodeHandlers[0x76] = () => {
399
+ this.halted = true;
400
+ return 4;
401
+ };
402
+
403
+ // LD r, n (immediate loads)
404
+ const ldRegImm = (reg) => {
405
+ this.registers[reg] = this.readMemory(this.registers.PC);
406
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
407
+ return 7;
408
+ };
409
+
410
+ this.opcodeHandlers[0x3e] = () => ldRegImm("A");
411
+ this.opcodeHandlers[0x06] = () => ldRegImm("B");
412
+ this.opcodeHandlers[0x0e] = () => ldRegImm("C");
413
+ this.opcodeHandlers[0x16] = () => ldRegImm("D");
414
+ this.opcodeHandlers[0x1e] = () => ldRegImm("E");
415
+ this.opcodeHandlers[0x26] = () => ldRegImm("H");
416
+ this.opcodeHandlers[0x2e] = () => ldRegImm("L");
417
+
418
+ // ADD A, r
419
+ this.opcodeHandlers[0x87] = () => this.addA(this.registers.A);
420
+ this.opcodeHandlers[0x80] = () => this.addA(this.registers.B);
421
+ this.opcodeHandlers[0x81] = () => this.addA(this.registers.C);
422
+ this.opcodeHandlers[0x82] = () => this.addA(this.registers.D);
423
+ this.opcodeHandlers[0x83] = () => this.addA(this.registers.E);
424
+ this.opcodeHandlers[0x84] = () => this.addA(this.registers.H);
425
+ this.opcodeHandlers[0x85] = () => this.addA(this.registers.L);
426
+
427
+ // SUB r
428
+ this.opcodeHandlers[0x97] = () => this.subA(this.registers.A);
429
+ this.opcodeHandlers[0x90] = () => this.subA(this.registers.B);
430
+ this.opcodeHandlers[0x91] = () => this.subA(this.registers.C);
431
+ this.opcodeHandlers[0x92] = () => this.subA(this.registers.D);
432
+ this.opcodeHandlers[0x93] = () => this.subA(this.registers.E);
433
+ this.opcodeHandlers[0x94] = () => this.subA(this.registers.H);
434
+ this.opcodeHandlers[0x95] = () => this.subA(this.registers.L);
435
+
436
+ // JP nn - unconditional jump
437
+ this.opcodeHandlers[0xc3] = () => {
438
+ const low = this.readMemory(this.registers.PC);
439
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
440
+ this.wz = (high << 8) | low;
441
+ this.registers.PC = (high << 8) | low;
442
+ return 10;
443
+ };
444
+
445
+ // JP Z, nn - conditional jump
446
+ this.opcodeHandlers[0xca] = () => {
447
+ const low = this.readMemory(this.registers.PC);
448
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
449
+ this.wz = (high << 8) | low;
450
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
451
+
452
+ if (this.flagZ) {
453
+ this.registers.PC = (high << 8) | low;
454
+ }
455
+ return 10;
456
+ };
457
+
458
+ // JP NZ, nn
459
+ this.opcodeHandlers[0xc2] = () => {
460
+ const low = this.readMemory(this.registers.PC);
461
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
462
+ this.wz = (high << 8) | low;
463
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
464
+
465
+ if (!this.flagZ) {
466
+ this.registers.PC = (high << 8) | low;
467
+ }
468
+ return 10;
469
+ };
470
+
471
+ // CP n - compare immediate
472
+ this.opcodeHandlers[0xfe] = () => {
473
+ const value = this.readMemory(this.registers.PC);
474
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
475
+ this.subA(value, true);
476
+ return 7;
477
+ };
478
+
479
+ // RET - return from subroutine
480
+ this.opcodeHandlers[0xc9] = () => {
481
+ const low = this.readMemory(this.registers.SP);
482
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
483
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
484
+ this.registers.PC = (high << 8) | low;
485
+ this.wz = this.registers.PC;
486
+ return 10;
487
+ };
488
+
489
+ // CALL nn - call subroutine
490
+ this.opcodeHandlers[0xcd] = () => {
491
+ const low = this.readMemory(this.registers.PC);
492
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
493
+ const targetAddr = (high << 8) | low;
494
+ this.wz = targetAddr;
495
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
496
+
497
+ // Push return address (little-endian)
498
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
499
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
500
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
501
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
502
+
503
+ this.registers.PC = targetAddr;
504
+ return 17;
505
+ };
506
+
507
+ // PUSH BC
508
+ // Stack grows downward. Push low byte (C) first, then high byte (B)
509
+ this.opcodeHandlers[0xc5] = () => {
510
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
511
+ this.writeMemory(this.registers.SP, this.registers.B); // High byte
512
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
513
+ this.writeMemory(this.registers.SP, this.registers.C); // Low byte
514
+ return 11;
515
+ };
516
+
517
+ // POP BC
518
+ this.opcodeHandlers[0xc1] = () => {
519
+ this.registers.C = this.readMemory(this.registers.SP);
520
+ this.registers.SP = (this.registers.SP + 1) & 0xffff;
521
+ this.registers.B = this.readMemory(this.registers.SP);
522
+ this.registers.SP = (this.registers.SP + 1) & 0xffff;
523
+ return 10;
524
+ };
525
+
526
+ // PUSH DE
527
+ this.opcodeHandlers[0xd5] = () => {
528
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
529
+ this.writeMemory(this.registers.SP, this.registers.D);
530
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
531
+ this.writeMemory(this.registers.SP, this.registers.E);
532
+ return 11;
533
+ };
534
+
535
+ // POP DE
536
+ this.opcodeHandlers[0xd1] = () => {
537
+ this.registers.E = this.readMemory(this.registers.SP);
538
+ this.registers.SP = (this.registers.SP + 1) & 0xffff;
539
+ this.registers.D = this.readMemory(this.registers.SP);
540
+ this.registers.SP = (this.registers.SP + 1) & 0xffff;
541
+ return 10;
542
+ };
543
+
544
+ // PUSH HL
545
+ this.opcodeHandlers[0xe5] = () => {
546
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
547
+ this.writeMemory(this.registers.SP, this.registers.H);
548
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
549
+ this.writeMemory(this.registers.SP, this.registers.L);
550
+ return 11;
551
+ };
552
+
553
+ // POP HL
554
+ this.opcodeHandlers[0xe1] = () => {
555
+ this.registers.L = this.readMemory(this.registers.SP);
556
+ this.registers.SP = (this.registers.SP + 1) & 0xffff;
557
+ this.registers.H = this.readMemory(this.registers.SP);
558
+ this.registers.SP = (this.registers.SP + 1) & 0xffff;
559
+ return 10;
560
+ };
561
+
562
+ // LD (HL), n
563
+ this.opcodeHandlers[0x36] = () => {
564
+ const value = this.readMemory(this.registers.PC);
565
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
566
+ this.writeMemory(this.HL, value);
567
+ return 10;
568
+ };
569
+
570
+ // LD A, (HL)
571
+ this.opcodeHandlers[0x7e] = () => {
572
+ this.registers.A = this.readMemory(this.HL);
573
+ return 7;
574
+ };
575
+
576
+ // INC r
577
+ this.opcodeHandlers[0x3c] = () => this.incReg("A");
578
+ this.opcodeHandlers[0x04] = () => this.incReg("B");
579
+ this.opcodeHandlers[0x0c] = () => this.incReg("C");
580
+ this.opcodeHandlers[0x14] = () => this.incReg("D");
581
+ this.opcodeHandlers[0x1c] = () => this.incReg("E");
582
+ this.opcodeHandlers[0x24] = () => this.incReg("H");
583
+ this.opcodeHandlers[0x2c] = () => this.incReg("L");
584
+
585
+ // DEC r
586
+ this.opcodeHandlers[0x3d] = () => this.decReg("A");
587
+ this.opcodeHandlers[0x05] = () => this.decReg("B");
588
+ this.opcodeHandlers[0x0d] = () => this.decReg("C");
589
+ this.opcodeHandlers[0x15] = () => this.decReg("D");
590
+ this.opcodeHandlers[0x1d] = () => this.decReg("E");
591
+ this.opcodeHandlers[0x25] = () => this.decReg("H");
592
+ this.opcodeHandlers[0x2d] = () => this.decReg("L");
593
+
594
+ // LD BC, nn (0x01)
595
+ this.opcodeHandlers[0x01] = () => {
596
+ const low = this.readMemory(this.registers.PC);
597
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
598
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
599
+ this.BC = (high << 8) | low;
600
+ return 10;
601
+ };
602
+
603
+ // LD DE, nn (0x11)
604
+ this.opcodeHandlers[0x11] = () => {
605
+ const low = this.readMemory(this.registers.PC);
606
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
607
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
608
+ this.DE = (high << 8) | low;
609
+ return 10;
610
+ };
611
+
612
+ // LD HL, nn
613
+ this.opcodeHandlers[0x21] = () => {
614
+ const low = this.readMemory(this.registers.PC);
615
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
616
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
617
+ this.HL = (high << 8) | low;
618
+ return 10;
619
+ };
620
+
621
+ // LD SP, nn (0x31)
622
+ this.opcodeHandlers[0x31] = () => {
623
+ const low = this.readMemory(this.registers.PC);
624
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
625
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
626
+ this.registers.SP = (high << 8) | low;
627
+ return 10;
628
+ };
629
+
630
+ // LD (nn), HL (0x22)
631
+ this.opcodeHandlers[0x22] = () => {
632
+ const low = this.readMemory(this.registers.PC);
633
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
634
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
635
+ const addr = (high << 8) | low;
636
+ this.wz = (addr + 1) & 0xffff;
637
+ this.writeMemory(addr, this.registers.L);
638
+ this.writeMemory((addr + 1) & 0xffff, this.registers.H);
639
+ return 16;
640
+ };
641
+
642
+ // LD HL, (nn) (0x2A)
643
+ this.opcodeHandlers[0x2a] = () => {
644
+ const low = this.readMemory(this.registers.PC);
645
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
646
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
647
+ const addr = (high << 8) | low;
648
+ this.wz = (addr + 1) & 0xffff;
649
+ this.registers.L = this.readMemory(addr);
650
+ this.registers.H = this.readMemory((addr + 1) & 0xffff);
651
+ return 16;
652
+ };
653
+
654
+ // LD (nn), A (0x32)
655
+ this.opcodeHandlers[0x32] = () => {
656
+ const low = this.readMemory(this.registers.PC);
657
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
658
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
659
+ const addr = (high << 8) | low;
660
+ this.writeMemory(addr, this.registers.A);
661
+ this.wz = ((addr + 1) & 0xff) | (this.registers.A << 8);
662
+ return 13;
663
+ };
664
+
665
+ // LD A, (nn) (0x3A)
666
+ this.opcodeHandlers[0x3a] = () => {
667
+ const low = this.readMemory(this.registers.PC);
668
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
669
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
670
+ const addr = (high << 8) | low;
671
+ this.registers.A = this.readMemory(addr);
672
+ this.wz = (addr + 1) & 0xffff;
673
+ return 13;
674
+ };
675
+
676
+ // LD A, (BC) (0x0A)
677
+ this.opcodeHandlers[0x0a] = () => {
678
+ this.registers.A = this.readMemory(this.BC);
679
+ this.wz = (this.BC + 1) & 0xffff;
680
+ return 7;
681
+ };
682
+
683
+ // LD A, (DE) (0x1A)
684
+ this.opcodeHandlers[0x1a] = () => {
685
+ this.registers.A = this.readMemory(this.DE);
686
+ this.wz = (this.DE + 1) & 0xffff;
687
+ return 7;
688
+ };
689
+
690
+ // LD (BC), A (0x02)
691
+ this.opcodeHandlers[0x02] = () => {
692
+ this.writeMemory(this.BC, this.registers.A);
693
+ this.wz = ((this.BC + 1) & 0xff) | (this.registers.A << 8);
694
+ return 7;
695
+ };
696
+
697
+ // LD (DE), A (0x12)
698
+ this.opcodeHandlers[0x12] = () => {
699
+ this.writeMemory(this.DE, this.registers.A);
700
+ this.wz = ((this.DE + 1) & 0xff) | (this.registers.A << 8);
701
+ return 7;
702
+ };
703
+
704
+ // OUT (n), A
705
+ this.opcodeHandlers[0xd3] = () => {
706
+ const port = this.readMemory(this.registers.PC);
707
+ this.wz = (this.registers.A << 8) | ((port + 1) & 0xff);
708
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
709
+ this.writePort(port, this.registers.A);
710
+ return 11;
711
+ };
712
+
713
+ // IN A, (n)
714
+ this.opcodeHandlers[0xdb] = () => {
715
+ const port = this.readMemory(this.registers.PC);
716
+ this.wz = (((this.registers.A << 8) | port) + 1) & 0xffff;
717
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
718
+ this.registers.A = this.readPort(port);
719
+ return 11;
720
+ };
721
+
722
+ // ADD A, (HL) (0x86)
723
+ this.opcodeHandlers[0x86] = () => {
724
+ this.addA(this.readMemory(this.HL));
725
+ return 7;
726
+ };
727
+
728
+ // ADC A, r (0x8F-0x8E)
729
+ this.opcodeHandlers[0x8f] = () => this.adcA(this.registers.A);
730
+ this.opcodeHandlers[0x88] = () => this.adcA(this.registers.B);
731
+ this.opcodeHandlers[0x89] = () => this.adcA(this.registers.C);
732
+ this.opcodeHandlers[0x8a] = () => this.adcA(this.registers.D);
733
+ this.opcodeHandlers[0x8b] = () => this.adcA(this.registers.E);
734
+ this.opcodeHandlers[0x8c] = () => this.adcA(this.registers.H);
735
+ this.opcodeHandlers[0x8d] = () => this.adcA(this.registers.L);
736
+ this.opcodeHandlers[0x8e] = () => {
737
+ this.adcA(this.readMemory(this.HL));
738
+ return 7;
739
+ };
740
+
741
+ // SBC A, r (0x9F-0x9E)
742
+ this.opcodeHandlers[0x9f] = () => this.sbcA(this.registers.A);
743
+ this.opcodeHandlers[0x98] = () => this.sbcA(this.registers.B);
744
+ this.opcodeHandlers[0x99] = () => this.sbcA(this.registers.C);
745
+ this.opcodeHandlers[0x9a] = () => this.sbcA(this.registers.D);
746
+ this.opcodeHandlers[0x9b] = () => this.sbcA(this.registers.E);
747
+ this.opcodeHandlers[0x9c] = () => this.sbcA(this.registers.H);
748
+ this.opcodeHandlers[0x9d] = () => this.sbcA(this.registers.L);
749
+ this.opcodeHandlers[0x9e] = () => {
750
+ this.sbcA(this.readMemory(this.HL));
751
+ return 7;
752
+ };
753
+
754
+ // SUB A, (HL) (0x96)
755
+ this.opcodeHandlers[0x96] = () => {
756
+ this.subA(this.readMemory(this.HL));
757
+ return 7;
758
+ };
759
+
760
+ // AND r (0xA0-0xA7)
761
+ this.opcodeHandlers[0xa7] = () => this.andA(this.registers.A);
762
+ this.opcodeHandlers[0xa0] = () => this.andA(this.registers.B);
763
+ this.opcodeHandlers[0xa1] = () => this.andA(this.registers.C);
764
+ this.opcodeHandlers[0xa2] = () => this.andA(this.registers.D);
765
+ this.opcodeHandlers[0xa3] = () => this.andA(this.registers.E);
766
+ this.opcodeHandlers[0xa4] = () => this.andA(this.registers.H);
767
+ this.opcodeHandlers[0xa5] = () => this.andA(this.registers.L);
768
+ this.opcodeHandlers[0xa6] = () => {
769
+ this.andA(this.readMemory(this.HL));
770
+ return 7;
771
+ };
772
+
773
+ // XOR r (0xA8-0xAF)
774
+ this.opcodeHandlers[0xaf] = () => this.xorA(this.registers.A);
775
+ this.opcodeHandlers[0xa8] = () => this.xorA(this.registers.B);
776
+ this.opcodeHandlers[0xa9] = () => this.xorA(this.registers.C);
777
+ this.opcodeHandlers[0xaa] = () => this.xorA(this.registers.D);
778
+ this.opcodeHandlers[0xab] = () => this.xorA(this.registers.E);
779
+ this.opcodeHandlers[0xac] = () => this.xorA(this.registers.H);
780
+ this.opcodeHandlers[0xad] = () => this.xorA(this.registers.L);
781
+ this.opcodeHandlers[0xae] = () => {
782
+ this.xorA(this.readMemory(this.HL));
783
+ return 7;
784
+ };
785
+
786
+ // OR r (0xB0-0xB7)
787
+ this.opcodeHandlers[0xb7] = () => this.orA(this.registers.A);
788
+ this.opcodeHandlers[0xb0] = () => this.orA(this.registers.B);
789
+ this.opcodeHandlers[0xb1] = () => this.orA(this.registers.C);
790
+ this.opcodeHandlers[0xb2] = () => this.orA(this.registers.D);
791
+ this.opcodeHandlers[0xb3] = () => this.orA(this.registers.E);
792
+ this.opcodeHandlers[0xb4] = () => this.orA(this.registers.H);
793
+ this.opcodeHandlers[0xb5] = () => this.orA(this.registers.L);
794
+ this.opcodeHandlers[0xb6] = () => {
795
+ this.orA(this.readMemory(this.HL));
796
+ return 7;
797
+ };
798
+
799
+ // LD r, r (0x40-0x7F) - register-to-register transfers
800
+ // Pattern: 0x40 | (dest << 3) | src
801
+ const regs = ["B", "C", "D", "E", "H", "L", null, "A"];
802
+ for (let dest = 0; dest < 8; dest++) {
803
+ for (let src = 0; src < 8; src++) {
804
+ if (dest === 6 || src === 6) continue; // Skip (HL) cases
805
+ const opcode = 0x40 | (dest << 3) | src;
806
+ const destReg = regs[dest];
807
+ const srcReg = regs[src];
808
+ this.opcodeHandlers[opcode] = () => {
809
+ this.registers[destReg] = this.registers[srcReg];
810
+ return 4;
811
+ };
812
+ }
813
+ }
814
+
815
+ // LD (HL), r (0x70-0x75)
816
+ this.opcodeHandlers[0x70] = () => {
817
+ this.writeMemory(this.HL, this.registers.B);
818
+ return 7;
819
+ };
820
+ this.opcodeHandlers[0x71] = () => {
821
+ this.writeMemory(this.HL, this.registers.C);
822
+ return 7;
823
+ };
824
+ this.opcodeHandlers[0x72] = () => {
825
+ this.writeMemory(this.HL, this.registers.D);
826
+ return 7;
827
+ };
828
+ this.opcodeHandlers[0x73] = () => {
829
+ this.writeMemory(this.HL, this.registers.E);
830
+ return 7;
831
+ };
832
+ this.opcodeHandlers[0x74] = () => {
833
+ this.writeMemory(this.HL, this.registers.H);
834
+ return 7;
835
+ };
836
+ this.opcodeHandlers[0x75] = () => {
837
+ this.writeMemory(this.HL, this.registers.L);
838
+ return 7;
839
+ };
840
+ this.opcodeHandlers[0x77] = () => {
841
+ this.writeMemory(this.HL, this.registers.A);
842
+ return 7;
843
+ };
844
+
845
+ // LD r, (HL) (0x46, 0x4E, 0x56, 0x5E, 0x66, 0x6E)
846
+ this.opcodeHandlers[0x46] = () => {
847
+ this.registers.B = this.readMemory(this.HL);
848
+ return 7;
849
+ };
850
+ this.opcodeHandlers[0x4e] = () => {
851
+ this.registers.C = this.readMemory(this.HL);
852
+ return 7;
853
+ };
854
+ this.opcodeHandlers[0x56] = () => {
855
+ this.registers.D = this.readMemory(this.HL);
856
+ return 7;
857
+ };
858
+ this.opcodeHandlers[0x5e] = () => {
859
+ this.registers.E = this.readMemory(this.HL);
860
+ return 7;
861
+ };
862
+ this.opcodeHandlers[0x66] = () => {
863
+ this.registers.H = this.readMemory(this.HL);
864
+ return 7;
865
+ };
866
+ this.opcodeHandlers[0x6e] = () => {
867
+ this.registers.L = this.readMemory(this.HL);
868
+ return 7;
869
+ };
870
+
871
+ // ADD A, n (0xC6)
872
+ this.opcodeHandlers[0xc6] = () => {
873
+ const value = this.readMemory(this.registers.PC);
874
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
875
+ this.addA(value);
876
+ return 7;
877
+ };
878
+
879
+ // ADC A, n (0xCE)
880
+ this.opcodeHandlers[0xce] = () => {
881
+ const value = this.readMemory(this.registers.PC);
882
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
883
+ this.adcA(value);
884
+ return 7;
885
+ };
886
+
887
+ // SUB A, n (0xD6)
888
+ this.opcodeHandlers[0xd6] = () => {
889
+ const value = this.readMemory(this.registers.PC);
890
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
891
+ this.subA(value);
892
+ return 7;
893
+ };
894
+
895
+ // SBC A, n (0xDE)
896
+ this.opcodeHandlers[0xde] = () => {
897
+ const value = this.readMemory(this.registers.PC);
898
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
899
+ this.sbcA(value);
900
+ return 7;
901
+ };
902
+
903
+ // AND n (0xE6)
904
+ this.opcodeHandlers[0xe6] = () => {
905
+ const value = this.readMemory(this.registers.PC);
906
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
907
+ this.andA(value);
908
+ return 7;
909
+ };
910
+
911
+ // XOR n (0xEE)
912
+ this.opcodeHandlers[0xee] = () => {
913
+ const value = this.readMemory(this.registers.PC);
914
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
915
+ this.xorA(value);
916
+ return 7;
917
+ };
918
+
919
+ // OR n (0xF6)
920
+ this.opcodeHandlers[0xf6] = () => {
921
+ const value = this.readMemory(this.registers.PC);
922
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
923
+ this.orA(value);
924
+ return 7;
925
+ };
926
+
927
+ // CP r (0xBF-0xBE)
928
+ this.opcodeHandlers[0xbf] = () => this.subA(this.registers.A, true);
929
+ this.opcodeHandlers[0xb8] = () => this.subA(this.registers.B, true);
930
+ this.opcodeHandlers[0xb9] = () => this.subA(this.registers.C, true);
931
+ this.opcodeHandlers[0xba] = () => this.subA(this.registers.D, true);
932
+ this.opcodeHandlers[0xbb] = () => this.subA(this.registers.E, true);
933
+ this.opcodeHandlers[0xbc] = () => this.subA(this.registers.H, true);
934
+ this.opcodeHandlers[0xbd] = () => this.subA(this.registers.L, true);
935
+ this.opcodeHandlers[0xbe] = () => {
936
+ this.subA(this.readMemory(this.HL), true);
937
+ return 7;
938
+ };
939
+
940
+ // INC (HL) (0x34)
941
+ this.opcodeHandlers[0x34] = () => {
942
+ const value = this.readMemory(this.HL);
943
+ const result = (value + 1) & 0xff;
944
+ this.writeMemory(this.HL, result);
945
+ this.flagZ = result === 0;
946
+ this.flagS = (result & 0x80) !== 0;
947
+ this.flagH = (value & 0x0f) === 0x0f;
948
+ this.flagPV = value === 0x7f;
949
+ this.flagN = 0;
950
+ this._xy(result);
951
+ return 11;
952
+ };
953
+
954
+ // DEC (HL) (0x35)
955
+ this.opcodeHandlers[0x35] = () => {
956
+ const value = this.readMemory(this.HL);
957
+ const result = (value - 1) & 0xff;
958
+ this.writeMemory(this.HL, result);
959
+ this.flagZ = result === 0;
960
+ this.flagS = (result & 0x80) !== 0;
961
+ this.flagH = (value & 0x0f) === 0x00;
962
+ this.flagPV = value === 0x80;
963
+ this.flagN = 1;
964
+ this._xy(result);
965
+ return 11;
966
+ };
967
+
968
+ // JP C, nn (0xDA)
969
+ this.opcodeHandlers[0xda] = () => {
970
+ const low = this.readMemory(this.registers.PC);
971
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
972
+ this.wz = (high << 8) | low;
973
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
974
+ if (this.flagC) {
975
+ this.registers.PC = (high << 8) | low;
976
+ }
977
+ return 10;
978
+ };
979
+
980
+ // JP NC, nn (0xD2)
981
+ this.opcodeHandlers[0xd2] = () => {
982
+ const low = this.readMemory(this.registers.PC);
983
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
984
+ this.wz = (high << 8) | low;
985
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
986
+ if (!this.flagC) {
987
+ this.registers.PC = (high << 8) | low;
988
+ }
989
+ return 10;
990
+ };
991
+
992
+ // JP P, nn (0xF2)
993
+ this.opcodeHandlers[0xf2] = () => {
994
+ const low = this.readMemory(this.registers.PC);
995
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
996
+ this.wz = (high << 8) | low;
997
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
998
+ if (!this.flagS) {
999
+ this.registers.PC = (high << 8) | low;
1000
+ }
1001
+ return 10;
1002
+ };
1003
+
1004
+ // JP M, nn (0xFA)
1005
+ this.opcodeHandlers[0xfa] = () => {
1006
+ const low = this.readMemory(this.registers.PC);
1007
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1008
+ this.wz = (high << 8) | low;
1009
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1010
+ if (this.flagS) {
1011
+ this.registers.PC = (high << 8) | low;
1012
+ }
1013
+ return 10;
1014
+ };
1015
+
1016
+ // JP PO, nn (0xE2)
1017
+ this.opcodeHandlers[0xe2] = () => {
1018
+ const low = this.readMemory(this.registers.PC);
1019
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1020
+ this.wz = (high << 8) | low;
1021
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1022
+ if (!this.flagPV) {
1023
+ this.registers.PC = (high << 8) | low;
1024
+ }
1025
+ return 10;
1026
+ };
1027
+
1028
+ // JP PE, nn (0xEA)
1029
+ this.opcodeHandlers[0xea] = () => {
1030
+ const low = this.readMemory(this.registers.PC);
1031
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1032
+ this.wz = (high << 8) | low;
1033
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1034
+ if (this.flagPV) {
1035
+ this.registers.PC = (high << 8) | low;
1036
+ }
1037
+ return 10;
1038
+ };
1039
+
1040
+ // JP (HL) (0xE9)
1041
+ this.opcodeHandlers[0xe9] = () => {
1042
+ this.registers.PC = this.HL;
1043
+ return 4;
1044
+ };
1045
+
1046
+ // JR e (0x18) - relative jump
1047
+ this.opcodeHandlers[0x18] = () => {
1048
+ const offset = this.readMemory(this.registers.PC);
1049
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1050
+ const signedOffset = offset & 0x80 ? offset - 256 : offset;
1051
+ this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
1052
+ this.wz = this.registers.PC;
1053
+ return 12;
1054
+ };
1055
+
1056
+ // JR Z, e (0x28)
1057
+ this.opcodeHandlers[0x28] = () => {
1058
+ const offset = this.readMemory(this.registers.PC);
1059
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1060
+ if (this.flagZ) {
1061
+ const signedOffset = offset & 0x80 ? offset - 256 : offset;
1062
+ this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
1063
+ this.wz = this.registers.PC;
1064
+ return 12;
1065
+ }
1066
+ return 7;
1067
+ };
1068
+
1069
+ // JR NZ, e (0x20)
1070
+ this.opcodeHandlers[0x20] = () => {
1071
+ const offset = this.readMemory(this.registers.PC);
1072
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1073
+ if (!this.flagZ) {
1074
+ const signedOffset = offset & 0x80 ? offset - 256 : offset;
1075
+ this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
1076
+ this.wz = this.registers.PC;
1077
+ return 12;
1078
+ }
1079
+ return 7;
1080
+ };
1081
+
1082
+ // JR C, e (0x38)
1083
+ this.opcodeHandlers[0x38] = () => {
1084
+ const offset = this.readMemory(this.registers.PC);
1085
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1086
+ if (this.flagC) {
1087
+ const signedOffset = offset & 0x80 ? offset - 256 : offset;
1088
+ this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
1089
+ this.wz = this.registers.PC;
1090
+ return 12;
1091
+ }
1092
+ return 7;
1093
+ };
1094
+
1095
+ // JR NC, e (0x30)
1096
+ this.opcodeHandlers[0x30] = () => {
1097
+ const offset = this.readMemory(this.registers.PC);
1098
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1099
+ if (!this.flagC) {
1100
+ const signedOffset = offset & 0x80 ? offset - 256 : offset;
1101
+ this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
1102
+ this.wz = this.registers.PC;
1103
+ return 12;
1104
+ }
1105
+ return 7;
1106
+ };
1107
+
1108
+ // DJNZ e (0x10)
1109
+ this.opcodeHandlers[0x10] = () => {
1110
+ const offset = this.readMemory(this.registers.PC);
1111
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1112
+ this.registers.B = (this.registers.B - 1) & 0xff;
1113
+ if (this.registers.B !== 0) {
1114
+ const signedOffset = offset & 0x80 ? offset - 256 : offset;
1115
+ this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
1116
+ this.wz = this.registers.PC;
1117
+ return 13;
1118
+ }
1119
+ return 8;
1120
+ };
1121
+
1122
+ // RET NZ (0xC0)
1123
+ this.opcodeHandlers[0xc0] = () => {
1124
+ if (!this.flagZ) {
1125
+ const low = this.readMemory(this.registers.SP);
1126
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
1127
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
1128
+ this.registers.PC = (high << 8) | low;
1129
+ this.wz = this.registers.PC;
1130
+ return 11;
1131
+ }
1132
+ return 5;
1133
+ };
1134
+
1135
+ // RET Z (0xC8)
1136
+ this.opcodeHandlers[0xc8] = () => {
1137
+ if (this.flagZ) {
1138
+ const low = this.readMemory(this.registers.SP);
1139
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
1140
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
1141
+ this.registers.PC = (high << 8) | low;
1142
+ this.wz = this.registers.PC;
1143
+ return 11;
1144
+ }
1145
+ return 5;
1146
+ };
1147
+
1148
+ // RET C (0xD8)
1149
+ this.opcodeHandlers[0xd8] = () => {
1150
+ if (this.flagC) {
1151
+ const low = this.readMemory(this.registers.SP);
1152
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
1153
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
1154
+ this.registers.PC = (high << 8) | low;
1155
+ this.wz = this.registers.PC;
1156
+ return 11;
1157
+ }
1158
+ return 5;
1159
+ };
1160
+
1161
+ // RET NC (0xD0)
1162
+ this.opcodeHandlers[0xd0] = () => {
1163
+ if (!this.flagC) {
1164
+ const low = this.readMemory(this.registers.SP);
1165
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
1166
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
1167
+ this.registers.PC = (high << 8) | low;
1168
+ this.wz = this.registers.PC;
1169
+ return 11;
1170
+ }
1171
+ return 5;
1172
+ };
1173
+
1174
+ // RET P (0xF0)
1175
+ this.opcodeHandlers[0xf0] = () => {
1176
+ if (!this.flagS) {
1177
+ const low = this.readMemory(this.registers.SP);
1178
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
1179
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
1180
+ this.registers.PC = (high << 8) | low;
1181
+ this.wz = this.registers.PC;
1182
+ return 11;
1183
+ }
1184
+ return 5;
1185
+ };
1186
+
1187
+ // RET M (0xF8)
1188
+ this.opcodeHandlers[0xf8] = () => {
1189
+ if (this.flagS) {
1190
+ const low = this.readMemory(this.registers.SP);
1191
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
1192
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
1193
+ this.registers.PC = (high << 8) | low;
1194
+ this.wz = this.registers.PC;
1195
+ return 11;
1196
+ }
1197
+ return 5;
1198
+ };
1199
+
1200
+ // RET PO (0xE0)
1201
+ this.opcodeHandlers[0xe0] = () => {
1202
+ if (!this.flagPV) {
1203
+ const low = this.readMemory(this.registers.SP);
1204
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
1205
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
1206
+ this.registers.PC = (high << 8) | low;
1207
+ this.wz = this.registers.PC;
1208
+ return 11;
1209
+ }
1210
+ return 5;
1211
+ };
1212
+
1213
+ // RET PE (0xE8)
1214
+ this.opcodeHandlers[0xe8] = () => {
1215
+ if (this.flagPV) {
1216
+ const low = this.readMemory(this.registers.SP);
1217
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
1218
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
1219
+ this.registers.PC = (high << 8) | low;
1220
+ this.wz = this.registers.PC;
1221
+ return 11;
1222
+ }
1223
+ return 5;
1224
+ };
1225
+
1226
+ // CALL Z, nn (0xCC)
1227
+ this.opcodeHandlers[0xcc] = () => {
1228
+ const low = this.readMemory(this.registers.PC);
1229
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1230
+ this.wz = (high << 8) | low;
1231
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1232
+ if (this.flagZ) {
1233
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1234
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
1235
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1236
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
1237
+ this.registers.PC = (high << 8) | low;
1238
+ return 17;
1239
+ }
1240
+ return 10;
1241
+ };
1242
+
1243
+ // CALL NZ, nn (0xC4)
1244
+ this.opcodeHandlers[0xc4] = () => {
1245
+ const low = this.readMemory(this.registers.PC);
1246
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1247
+ this.wz = (high << 8) | low;
1248
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1249
+ if (!this.flagZ) {
1250
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1251
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
1252
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1253
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
1254
+ this.registers.PC = (high << 8) | low;
1255
+ return 17;
1256
+ }
1257
+ return 10;
1258
+ };
1259
+
1260
+ // CALL C, nn (0xDC)
1261
+ this.opcodeHandlers[0xdc] = () => {
1262
+ const low = this.readMemory(this.registers.PC);
1263
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1264
+ this.wz = (high << 8) | low;
1265
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1266
+ if (this.flagC) {
1267
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1268
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
1269
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1270
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
1271
+ this.registers.PC = (high << 8) | low;
1272
+ return 17;
1273
+ }
1274
+ return 10;
1275
+ };
1276
+
1277
+ // CALL NC, nn (0xD4)
1278
+ this.opcodeHandlers[0xd4] = () => {
1279
+ const low = this.readMemory(this.registers.PC);
1280
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1281
+ this.wz = (high << 8) | low;
1282
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1283
+ if (!this.flagC) {
1284
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1285
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
1286
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1287
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
1288
+ this.registers.PC = (high << 8) | low;
1289
+ return 17;
1290
+ }
1291
+ return 10;
1292
+ };
1293
+
1294
+ // CALL P, nn (0xF4)
1295
+ this.opcodeHandlers[0xf4] = () => {
1296
+ const low = this.readMemory(this.registers.PC);
1297
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1298
+ this.wz = (high << 8) | low;
1299
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1300
+ if (!this.flagS) {
1301
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1302
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
1303
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1304
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
1305
+ this.registers.PC = (high << 8) | low;
1306
+ return 17;
1307
+ }
1308
+ return 10;
1309
+ };
1310
+
1311
+ // CALL M, nn (0xFC)
1312
+ this.opcodeHandlers[0xfc] = () => {
1313
+ const low = this.readMemory(this.registers.PC);
1314
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1315
+ this.wz = (high << 8) | low;
1316
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1317
+ if (this.flagS) {
1318
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1319
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
1320
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1321
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
1322
+ this.registers.PC = (high << 8) | low;
1323
+ return 17;
1324
+ }
1325
+ return 10;
1326
+ };
1327
+
1328
+ // CALL PO, nn (0xE4)
1329
+ this.opcodeHandlers[0xe4] = () => {
1330
+ const low = this.readMemory(this.registers.PC);
1331
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1332
+ this.wz = (high << 8) | low;
1333
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1334
+ if (!this.flagPV) {
1335
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1336
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
1337
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1338
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
1339
+ this.registers.PC = (high << 8) | low;
1340
+ return 17;
1341
+ }
1342
+ return 10;
1343
+ };
1344
+
1345
+ // CALL PE, nn (0xEC)
1346
+ this.opcodeHandlers[0xec] = () => {
1347
+ const low = this.readMemory(this.registers.PC);
1348
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
1349
+ this.wz = (high << 8) | low;
1350
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
1351
+ if (this.flagPV) {
1352
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1353
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
1354
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1355
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
1356
+ this.registers.PC = (high << 8) | low;
1357
+ return 17;
1358
+ }
1359
+ return 10;
1360
+ };
1361
+
1362
+ // RST p (0xC7-0xFF, step 8)
1363
+ for (let i = 0; i < 8; i++) {
1364
+ const opcode = 0xc7 | (i << 3);
1365
+ const addr = i * 8;
1366
+ this.opcodeHandlers[opcode] = () => {
1367
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1368
+ this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
1369
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1370
+ this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
1371
+ this.registers.PC = addr;
1372
+ this.wz = addr;
1373
+ return 11;
1374
+ };
1375
+ }
1376
+
1377
+ // DI (0xF3)
1378
+ this.opcodeHandlers[0xf3] = () => {
1379
+ this.IFF1 = false;
1380
+ this.IFF2 = false;
1381
+ this.pendingEI = 0; // a DI right after EI wins
1382
+ return 4;
1383
+ };
1384
+
1385
+ // EI (0xFB) - takes effect after the NEXT instruction completes, so
1386
+ // an EI;RETI pair can't be interrupted between the two instructions.
1387
+ this.opcodeHandlers[0xfb] = () => {
1388
+ this.pendingEI = 2;
1389
+ return 4;
1390
+ };
1391
+
1392
+ // SCF (0x37) - Set Carry Flag
1393
+ this.opcodeHandlers[0x37] = () => {
1394
+ this.flagC = true;
1395
+ this.flagH = false;
1396
+ this.flagN = false;
1397
+ this._xy(this.registers.A);
1398
+ return 4;
1399
+ };
1400
+
1401
+ // CCF (0x3F) - Complement Carry Flag; H receives the previous carry
1402
+ this.opcodeHandlers[0x3f] = () => {
1403
+ const oldCarry = this.flagC;
1404
+ this.flagC = !oldCarry;
1405
+ this.flagH = oldCarry;
1406
+ this.flagN = false;
1407
+ this._xy(this.registers.A);
1408
+ return 4;
1409
+ };
1410
+
1411
+ // CPL (0x2F) - Complement Accumulator
1412
+ this.opcodeHandlers[0x2f] = () => {
1413
+ this.registers.A = ~this.registers.A & 0xff;
1414
+ this.flagH = true;
1415
+ this.flagN = true;
1416
+ this._xy(this.registers.A);
1417
+ return 4;
1418
+ };
1419
+
1420
+ // DAA (0x27) - Decimal Adjust Accumulator (canonical N/H/C correction)
1421
+ this.opcodeHandlers[0x27] = () => {
1422
+ const before = this.registers.A;
1423
+ let correction = 0;
1424
+ let carry = this.flagC ? 1 : 0;
1425
+
1426
+ if (this.flagH || (before & 0x0f) > 9) {
1427
+ correction = 0x06;
1428
+ }
1429
+ if (carry || before > 0x99) {
1430
+ correction |= 0x60;
1431
+ carry = 1;
1432
+ }
1433
+
1434
+ const a = this.flagN
1435
+ ? (before - correction) & 0xff
1436
+ : (before + correction) & 0xff;
1437
+
1438
+ // H after DAA: for additions, carry out of bit 3 of the correction
1439
+ // step; for subtractions, only when the low nibble borrows.
1440
+ this.flagH = this.flagN
1441
+ ? this.flagH && (before & 0x0f) < 0x06
1442
+ : (before & 0x0f) + (correction & 0x0f) > 0x0f;
1443
+ this.flagC = carry;
1444
+ this.registers.A = a;
1445
+ this.flagZ = a === 0;
1446
+ this.flagS = (a & 0x80) !== 0;
1447
+ this.flagPV = this.parity(a);
1448
+ this._xy(a);
1449
+ return 4;
1450
+ };
1451
+
1452
+ // RLCA (0x07)
1453
+ this.opcodeHandlers[0x07] = () => {
1454
+ const a = this.registers.A;
1455
+ this.flagC = (a & 0x80) !== 0;
1456
+ this.registers.A = ((a << 1) | (a >> 7)) & 0xff;
1457
+ this.flagH = false;
1458
+ this.flagN = false;
1459
+ this._xy(this.registers.A);
1460
+ return 4;
1461
+ };
1462
+
1463
+ // RLA (0x17)
1464
+ this.opcodeHandlers[0x17] = () => {
1465
+ const a = this.registers.A;
1466
+ const carry = this.flagC ? 1 : 0;
1467
+ this.flagC = (a & 0x80) !== 0;
1468
+ this.registers.A = ((a << 1) | carry) & 0xff;
1469
+ this.flagH = false;
1470
+ this.flagN = false;
1471
+ this._xy(this.registers.A);
1472
+ return 4;
1473
+ };
1474
+
1475
+ // RRCA (0x0F)
1476
+ this.opcodeHandlers[0x0f] = () => {
1477
+ const a = this.registers.A;
1478
+ this.flagC = (a & 0x01) !== 0;
1479
+ this.registers.A = ((a >> 1) | (a << 7)) & 0xff;
1480
+ this.flagH = false;
1481
+ this.flagN = false;
1482
+ this._xy(this.registers.A);
1483
+ return 4;
1484
+ };
1485
+
1486
+ // RRA (0x1F)
1487
+ this.opcodeHandlers[0x1f] = () => {
1488
+ const a = this.registers.A;
1489
+ const carry = this.flagC ? 0x80 : 0;
1490
+ this.flagC = (a & 0x01) !== 0;
1491
+ this.registers.A = ((a >> 1) | carry) & 0xff;
1492
+ this.flagH = false;
1493
+ this.flagN = false;
1494
+ this._xy(this.registers.A);
1495
+ return 4;
1496
+ };
1497
+
1498
+ // EX DE, HL (0xEB)
1499
+ this.opcodeHandlers[0xeb] = () => {
1500
+ const temp = this.DE;
1501
+ this.DE = this.HL;
1502
+ this.HL = temp;
1503
+ return 4;
1504
+ };
1505
+
1506
+ // EX AF, AF' (0x08)
1507
+ this.opcodeHandlers[0x08] = () => {
1508
+ const tempA = this.registers.A;
1509
+ const tempF = this.registers.F;
1510
+ this.registers.A = this.registers.A_;
1511
+ this.registers.F = this.registers.F_;
1512
+ this.registers.A_ = tempA;
1513
+ this.registers.F_ = tempF;
1514
+ return 4;
1515
+ };
1516
+
1517
+ // EX (SP), HL (0xE3)
1518
+ this.opcodeHandlers[0xe3] = () => {
1519
+ const low = this.readMemory(this.registers.SP);
1520
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
1521
+ const temp = (high << 8) | low;
1522
+ this.writeMemory(this.registers.SP, this.registers.L);
1523
+ this.writeMemory((this.registers.SP + 1) & 0xffff, this.registers.H);
1524
+ this.HL = temp;
1525
+ this.wz = temp;
1526
+ return 19;
1527
+ };
1528
+
1529
+ // EXX (0xD9)
1530
+ this.opcodeHandlers[0xd9] = () => {
1531
+ const tempB = this.registers.B;
1532
+ const tempC = this.registers.C;
1533
+ const tempD = this.registers.D;
1534
+ const tempE = this.registers.E;
1535
+ const tempH = this.registers.H;
1536
+ const tempL = this.registers.L;
1537
+ this.registers.B = this.registers.B_;
1538
+ this.registers.C = this.registers.C_;
1539
+ this.registers.D = this.registers.D_;
1540
+ this.registers.E = this.registers.E_;
1541
+ this.registers.H = this.registers.H_;
1542
+ this.registers.L = this.registers.L_;
1543
+ this.registers.B_ = tempB;
1544
+ this.registers.C_ = tempC;
1545
+ this.registers.D_ = tempD;
1546
+ this.registers.E_ = tempE;
1547
+ this.registers.H_ = tempH;
1548
+ this.registers.L_ = tempL;
1549
+ return 4;
1550
+ };
1551
+
1552
+ // ADD HL, BC (0x09)
1553
+ this.opcodeHandlers[0x09] = () => {
1554
+ this.addHL(this.BC);
1555
+ return 11;
1556
+ };
1557
+
1558
+ // ADD HL, DE (0x19)
1559
+ this.opcodeHandlers[0x19] = () => {
1560
+ this.addHL(this.DE);
1561
+ return 11;
1562
+ };
1563
+
1564
+ // ADD HL, HL (0x29)
1565
+ this.opcodeHandlers[0x29] = () => {
1566
+ this.addHL(this.HL);
1567
+ return 11;
1568
+ };
1569
+
1570
+ // ADD HL, SP (0x39)
1571
+ this.opcodeHandlers[0x39] = () => {
1572
+ this.addHL(this.registers.SP);
1573
+ return 11;
1574
+ };
1575
+
1576
+ // PUSH AF (0xF5)
1577
+ this.opcodeHandlers[0xf5] = () => {
1578
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1579
+ this.writeMemory(this.registers.SP, this.registers.A);
1580
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1581
+ this.writeMemory(this.registers.SP, this.registers.F);
1582
+ return 11;
1583
+ };
1584
+
1585
+ // POP AF (0xF1)
1586
+ this.opcodeHandlers[0xf1] = () => {
1587
+ this.registers.F = this.readMemory(this.registers.SP);
1588
+ this.registers.SP = (this.registers.SP + 1) & 0xffff;
1589
+ this.registers.A = this.readMemory(this.registers.SP);
1590
+ this.registers.SP = (this.registers.SP + 1) & 0xffff;
1591
+ return 10;
1592
+ };
1593
+
1594
+ // INC BC (0x03)
1595
+ this.opcodeHandlers[0x03] = () => {
1596
+ this.BC = (this.BC + 1) & 0xffff;
1597
+ return 6;
1598
+ };
1599
+
1600
+ // INC DE (0x13)
1601
+ this.opcodeHandlers[0x13] = () => {
1602
+ this.DE = (this.DE + 1) & 0xffff;
1603
+ return 6;
1604
+ };
1605
+
1606
+ // INC HL (0x23)
1607
+ this.opcodeHandlers[0x23] = () => {
1608
+ this.HL = (this.HL + 1) & 0xffff;
1609
+ return 6;
1610
+ };
1611
+
1612
+ // INC SP (0x33)
1613
+ this.opcodeHandlers[0x33] = () => {
1614
+ this.registers.SP = (this.registers.SP + 1) & 0xffff;
1615
+ return 6;
1616
+ };
1617
+
1618
+ // DEC BC (0x0B)
1619
+ this.opcodeHandlers[0x0b] = () => {
1620
+ this.BC = (this.BC - 1) & 0xffff;
1621
+ return 6;
1622
+ };
1623
+
1624
+ // DEC DE (0x1B)
1625
+ this.opcodeHandlers[0x1b] = () => {
1626
+ this.DE = (this.DE - 1) & 0xffff;
1627
+ return 6;
1628
+ };
1629
+
1630
+ // DEC HL (0x2B)
1631
+ this.opcodeHandlers[0x2b] = () => {
1632
+ this.HL = (this.HL - 1) & 0xffff;
1633
+ return 6;
1634
+ };
1635
+
1636
+ // DEC SP (0x3B)
1637
+ this.opcodeHandlers[0x3b] = () => {
1638
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
1639
+ return 6;
1640
+ };
1641
+
1642
+ // LD SP, HL (0xF9)
1643
+ this.opcodeHandlers[0xf9] = () => {
1644
+ this.registers.SP = this.HL;
1645
+ return 6;
1646
+ };
1647
+ }
1648
+
1649
+ // Extended instruction handlers - ALL must be fully implemented
1650
+ executeCB() {
1651
+ const opcode = this.readMemory(this.registers.PC);
1652
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1653
+
1654
+ // CB instructions (bit operations, rotates, shifts)
1655
+ // Pattern: opcode = base | (register << 3) | register
1656
+ // Register mapping: 0=B, 1=C, 2=D, 3=E, 4=H, 5=L, 6=(HL), 7=A
1657
+
1658
+ const regs = ["B", "C", "D", "E", "H", "L", null, "A"];
1659
+ const reg = opcode & 0x07;
1660
+ const base = opcode & 0xf8;
1661
+
1662
+ if (reg === 6) {
1663
+ // (HL) operations - 15 cycles
1664
+ switch (base) {
1665
+ case 0x00: // RLC (HL)
1666
+ return this.rlcMem(this.HL);
1667
+ case 0x08: // RRC (HL)
1668
+ return this.rrcMem(this.HL);
1669
+ case 0x10: // RL (HL)
1670
+ return this.rlMem(this.HL);
1671
+ case 0x18: // RR (HL)
1672
+ return this.rrMem(this.HL);
1673
+ case 0x20: // SLA (HL)
1674
+ return this.slaMem(this.HL);
1675
+ case 0x28: // SRA (HL)
1676
+ return this.sraMem(this.HL);
1677
+ case 0x30: // SLL (HL) - undocumented
1678
+ return this.sllMem(this.HL);
1679
+ case 0x38: // SRL (HL)
1680
+ return this.srlMem(this.HL);
1681
+ case 0x40:
1682
+ case 0x48:
1683
+ case 0x50:
1684
+ case 0x58:
1685
+ case 0x60:
1686
+ case 0x68:
1687
+ case 0x70:
1688
+ case 0x78: // BIT n, (HL)
1689
+ return this.bitMem(this.HL, (base >> 3) & 0x07);
1690
+ case 0x80:
1691
+ case 0x88:
1692
+ case 0x90:
1693
+ case 0x98:
1694
+ case 0xa0:
1695
+ case 0xa8:
1696
+ case 0xb0:
1697
+ case 0xb8: // RES n, (HL)
1698
+ return this.resMem(this.HL, (base >> 3) & 0x07);
1699
+ case 0xc0:
1700
+ case 0xc8:
1701
+ case 0xd0:
1702
+ case 0xd8:
1703
+ case 0xe0:
1704
+ case 0xe8:
1705
+ case 0xf0:
1706
+ case 0xf8: // SET n, (HL)
1707
+ return this.setMem(this.HL, (base >> 3) & 0x07);
1708
+ default:
1709
+ if (!this.unimplementedOpcodes.has(0xcb00 | opcode)) {
1710
+ this.unimplementedOpcodes.add(0xcb00 | opcode);
1711
+ this.log("warn",
1712
+ `Unimplemented CB opcode: 0xCB 0x${opcode
1713
+ .toString(16)
1714
+ .toUpperCase()
1715
+ .padStart(2, "0")}`
1716
+ );
1717
+ }
1718
+ return 8;
1719
+ }
1720
+ } else {
1721
+ // Register operations - 8 cycles
1722
+ const regName = regs[reg];
1723
+ switch (base) {
1724
+ case 0x00: // RLC r
1725
+ return this.rlcReg(regName);
1726
+ case 0x08: // RRC r
1727
+ return this.rrcReg(regName);
1728
+ case 0x10: // RL r
1729
+ return this.rlReg(regName);
1730
+ case 0x18: // RR r
1731
+ return this.rrReg(regName);
1732
+ case 0x20: // SLA r
1733
+ return this.slaReg(regName);
1734
+ case 0x28: // SRA r
1735
+ return this.sraReg(regName);
1736
+ case 0x30: // SLL r - undocumented
1737
+ return this.sllReg(regName);
1738
+ case 0x38: // SRL r
1739
+ return this.srlReg(regName);
1740
+ case 0x40:
1741
+ case 0x48:
1742
+ case 0x50:
1743
+ case 0x58:
1744
+ case 0x60:
1745
+ case 0x68:
1746
+ case 0x70:
1747
+ case 0x78: // BIT n, r
1748
+ return this.bitReg(regName, (base >> 3) & 0x07);
1749
+ case 0x80:
1750
+ case 0x88:
1751
+ case 0x90:
1752
+ case 0x98:
1753
+ case 0xa0:
1754
+ case 0xa8:
1755
+ case 0xb0:
1756
+ case 0xb8: // RES n, r
1757
+ return this.resReg(regName, (base >> 3) & 0x07);
1758
+ case 0xc0:
1759
+ case 0xc8:
1760
+ case 0xd0:
1761
+ case 0xd8:
1762
+ case 0xe0:
1763
+ case 0xe8:
1764
+ case 0xf0:
1765
+ case 0xf8: // SET n, r
1766
+ return this.setReg(regName, (base >> 3) & 0x07);
1767
+ default:
1768
+ if (!this.unimplementedOpcodes.has(0xcb00 | opcode)) {
1769
+ this.unimplementedOpcodes.add(0xcb00 | opcode);
1770
+ this.log("warn",
1771
+ `Unimplemented CB opcode: 0xCB 0x${opcode
1772
+ .toString(16)
1773
+ .toUpperCase()
1774
+ .padStart(2, "0")}`
1775
+ );
1776
+ }
1777
+ return 8;
1778
+ }
1779
+ }
1780
+ }
1781
+
1782
+ executeED() {
1783
+ const opcode = this.readMemory(this.registers.PC);
1784
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1785
+
1786
+ // ED instructions (extended instructions, block operations, I/O)
1787
+ switch (opcode) {
1788
+ // Block Transfer Instructions
1789
+ case 0xa0: // LDI
1790
+ return this.ldi();
1791
+ case 0xb0: // LDIR
1792
+ return this.ldir();
1793
+ case 0xa8: // LDD
1794
+ return this.ldd();
1795
+ case 0xb8: // LDDR
1796
+ return this.lddr();
1797
+
1798
+ // Nibble Rotation Instructions
1799
+ case 0x6f: // RLD
1800
+ return this.rld();
1801
+ case 0x67: // RRD
1802
+ return this.rrd();
1803
+
1804
+ // Block Compare Instructions
1805
+ case 0xa1: // CPI
1806
+ return this.cpi();
1807
+ case 0xb1: // CPIR
1808
+ return this.cpir();
1809
+ case 0xa9: // CPD
1810
+ return this.cpd();
1811
+ case 0xb9: // CPDR
1812
+ return this.cpdr();
1813
+
1814
+ // Block Input Instructions
1815
+ case 0xa2: // INI
1816
+ return this.ini();
1817
+ case 0xb2: // INIR
1818
+ return this.inir();
1819
+ case 0xaa: // IND
1820
+ return this.ind();
1821
+ case 0xba: // INDR
1822
+ return this.indr();
1823
+
1824
+ // Block Output Instructions
1825
+ case 0xa3: // OUTI
1826
+ return this.outi();
1827
+ case 0xb3: // OTIR
1828
+ return this.otir();
1829
+ case 0xab: // OUTD
1830
+ return this.outd();
1831
+ case 0xbb: // OTDR
1832
+ return this.otdr();
1833
+
1834
+ // Extended Load Instructions
1835
+ case 0x47: // LD I, A
1836
+ this.registers.I = this.registers.A;
1837
+ return 9;
1838
+ case 0x4f: // LD R, A
1839
+ this.registers.R = this.registers.A;
1840
+ return 9;
1841
+ case 0x57: // LD A, I
1842
+ this.registers.A = this.registers.I;
1843
+ this.flagZ = this.registers.A === 0;
1844
+ this.flagS = (this.registers.A & 0x80) !== 0;
1845
+ this.flagH = false;
1846
+ this.flagPV = this.IFF2;
1847
+ this.flagN = false;
1848
+ this._xy(this.registers.A);
1849
+ return 9;
1850
+ case 0x5f: // LD A, R
1851
+ this.registers.A = this.registers.R;
1852
+ this.flagZ = this.registers.A === 0;
1853
+ this.flagS = (this.registers.A & 0x80) !== 0;
1854
+ this.flagH = false;
1855
+ this.flagPV = this.IFF2;
1856
+ this.flagN = false;
1857
+ this._xy(this.registers.A);
1858
+ return 9;
1859
+
1860
+ // 16-bit Load Instructions
1861
+ case 0x4b: // LD BC, (nn)
1862
+ return this.ldBCnn();
1863
+ case 0x5b: // LD DE, (nn)
1864
+ return this.ldDEnn();
1865
+ case 0x6b: // LD HL, (nn) - alternative encoding
1866
+ return this.ldHLnn();
1867
+ case 0x7b: // LD SP, (nn)
1868
+ return this.ldSPnn();
1869
+ case 0x43: // LD (nn), BC
1870
+ return this.ldnnBC();
1871
+ case 0x53: // LD (nn), DE
1872
+ return this.ldnnDE();
1873
+ case 0x63: // LD (nn), HL - alternative encoding
1874
+ return this.ldnnHL();
1875
+ case 0x73: // LD (nn), SP
1876
+ return this.ldnnSP();
1877
+
1878
+ // Extended Arithmetic
1879
+ case 0x4a: // ADC HL, BC
1880
+ return this.adcHL(this.BC);
1881
+ case 0x5a: // ADC HL, DE
1882
+ return this.adcHL(this.DE);
1883
+ case 0x6a: // ADC HL, HL
1884
+ return this.adcHL(this.HL);
1885
+ case 0x7a: // ADC HL, SP
1886
+ return this.adcHL(this.registers.SP);
1887
+ case 0x42: // SBC HL, BC
1888
+ return this.sbcHL(this.BC);
1889
+ case 0x52: // SBC HL, DE
1890
+ return this.sbcHL(this.DE);
1891
+ case 0x62: // SBC HL, HL
1892
+ return this.sbcHL(this.HL);
1893
+ case 0x72: // SBC HL, SP
1894
+ return this.sbcHL(this.registers.SP);
1895
+
1896
+ // NEG and its duplicates
1897
+ case 0x44:
1898
+ case 0x4c:
1899
+ case 0x54:
1900
+ case 0x5c:
1901
+ case 0x64:
1902
+ case 0x6c:
1903
+ case 0x74:
1904
+ case 0x7c:
1905
+ return this.neg();
1906
+
1907
+ // I/O Instructions
1908
+ case 0x40:
1909
+ case 0x48:
1910
+ case 0x50:
1911
+ case 0x58:
1912
+ case 0x60:
1913
+ case 0x68:
1914
+ case 0x70:
1915
+ case 0x78: // IN r, (C)
1916
+ return this.inrC((opcode >> 3) & 0x07);
1917
+ case 0x41:
1918
+ case 0x49:
1919
+ case 0x51:
1920
+ case 0x59:
1921
+ case 0x61:
1922
+ case 0x69:
1923
+ case 0x71:
1924
+ case 0x79: // OUT (C), r
1925
+ return this.outCr((opcode >> 3) & 0x07);
1926
+
1927
+ // Return Instructions
1928
+ case 0x4d: // RETI
1929
+ return this.reti();
1930
+ case 0x55:
1931
+ case 0x5d:
1932
+ case 0x65:
1933
+ case 0x6d:
1934
+ case 0x75:
1935
+ case 0x7d:
1936
+ case 0x45: // RETN
1937
+ return this.retn();
1938
+
1939
+ // Interrupt Mode
1940
+ case 0x4e:
1941
+ case 0x66:
1942
+ case 0x6e:
1943
+ case 0x46: // IM 0
1944
+ this.interruptMode = 0;
1945
+ return 8;
1946
+ case 0x76:
1947
+ case 0x56: // IM 1
1948
+ this.interruptMode = 1;
1949
+ return 8;
1950
+ case 0x7e:
1951
+ case 0x5e: // IM 2
1952
+ this.interruptMode = 2;
1953
+ return 8;
1954
+
1955
+ // Every other ED opcode does nothing for 8 T-states (Young §3.3)
1956
+ default:
1957
+ return 8;
1958
+ }
1959
+ }
1960
+
1961
+ executeDD() {
1962
+ const opcode = this.readMemory(this.registers.PC);
1963
+ // Before another prefix or ED, DD is a 4-T-state no-op: leave PC on that byte, which starts
1964
+ // the next step, and take no maskable interrupt before it (Young §3.6).
1965
+ if (opcode === 0xdd || opcode === 0xfd || opcode === 0xed) {
1966
+ this.afterPrefix = true;
1967
+ return 4;
1968
+ }
1969
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1970
+ this.incrementR(); // the second opcode fetch
1971
+
1972
+ // DD instructions (IX register operations)
1973
+ // Most DD instructions mirror HL instructions but use IX
1974
+ // Special cases: DD CB d opcode - indexed CB operations
1975
+ if (opcode === 0xcb) {
1976
+ // DD CB d opcode - indexed CB operation
1977
+ const disp = this.readMemory(this.registers.PC);
1978
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1979
+ const cbOpcode = this.readMemory(this.registers.PC);
1980
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1981
+ const addr = (this.IX + (disp & 0x80 ? disp - 256 : disp)) & 0xffff;
1982
+ return this.executeIndexedCB(addr, cbOpcode);
1983
+ }
1984
+
1985
+ // Standard DD instructions - delegate to helper
1986
+ return this.executeIndexReg(opcode, "IX");
1987
+ }
1988
+
1989
+ executeFD() {
1990
+ const opcode = this.readMemory(this.registers.PC);
1991
+ // Before another prefix or ED, FD is a 4-T-state no-op: leave PC on that byte, which starts
1992
+ // the next step, and take no maskable interrupt before it (Young §3.6).
1993
+ if (opcode === 0xdd || opcode === 0xfd || opcode === 0xed) {
1994
+ this.afterPrefix = true;
1995
+ return 4;
1996
+ }
1997
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
1998
+ this.incrementR(); // the second opcode fetch
1999
+
2000
+ // FD instructions (IY register operations)
2001
+ // Most FD instructions mirror HL instructions but use IY
2002
+ // Special cases: FD CB d opcode - indexed CB operations
2003
+ if (opcode === 0xcb) {
2004
+ // FD CB d opcode - indexed CB operation
2005
+ const disp = this.readMemory(this.registers.PC);
2006
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
2007
+ const cbOpcode = this.readMemory(this.registers.PC);
2008
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
2009
+ const addr = (this.IY + (disp & 0x80 ? disp - 256 : disp)) & 0xffff;
2010
+ return this.executeIndexedCB(addr, cbOpcode);
2011
+ }
2012
+
2013
+ // Standard FD instructions - delegate to helper
2014
+ return this.executeIndexReg(opcode, "IY");
2015
+ }
2016
+
2017
+ // Arithmetic methods
2018
+ /**
2019
+ * Add a value to the accumulator (A register) with flag updates
2020
+ */
2021
+ addA(value, withCarry = false) {
2022
+ const a = this.registers.A;
2023
+ const carry = withCarry && this.flagC ? 1 : 0;
2024
+ const result = a + value + carry;
2025
+
2026
+ this.flagC = result > 0xff;
2027
+ // Half-carry: carry out of bit 3 in the nibble sum
2028
+ this.flagH = (a & 0x0f) + (value & 0x0f) + carry > 0x0f;
2029
+ this.flagN = 0;
2030
+ this.registers.A = result & 0xff;
2031
+ this.flagZ = this.registers.A === 0;
2032
+ this.flagS = (this.registers.A & 0x80) !== 0;
2033
+ // Parity/Overflow: signed overflow
2034
+ const signedA = a & 0x80 ? a - 256 : a;
2035
+ const signedValue = value & 0x80 ? value - 256 : value;
2036
+ const signedResult = signedA + signedValue + carry;
2037
+ this.flagPV = signedResult < -128 || signedResult > 127;
2038
+ this._xy(this.registers.A);
2039
+
2040
+ return 4;
2041
+ }
2042
+
2043
+ /**
2044
+ * Subtract a value from the accumulator with flag updates
2045
+ */
2046
+ subA(value, compare = false) {
2047
+ const a = this.registers.A;
2048
+ const result = a - value;
2049
+
2050
+ this.flagC = result < 0;
2051
+ this.flagH = (a & 0x0f) - (value & 0x0f) < 0;
2052
+ this.flagN = 1;
2053
+
2054
+ if (!compare) {
2055
+ this.registers.A = result & 0xff;
2056
+ }
2057
+
2058
+ const finalResult = result & 0xff;
2059
+ this.flagZ = finalResult === 0;
2060
+ this.flagS = (finalResult & 0x80) !== 0;
2061
+ this.flagPV = ((a ^ value) & (a ^ finalResult) & 0x80) !== 0;
2062
+ // CP is the X/Y exception: bits 3/5 come from the operand
2063
+ this._xy(compare ? value : finalResult);
2064
+
2065
+ return 4;
2066
+ }
2067
+
2068
+ /**
2069
+ * Increment a register with flag updates (except carry)
2070
+ */
2071
+ incReg(reg) {
2072
+ const value = this.registers[reg];
2073
+ const result = (value + 1) & 0xff;
2074
+
2075
+ this.registers[reg] = result;
2076
+ this.flagZ = result === 0;
2077
+ this.flagS = (result & 0x80) !== 0;
2078
+ this.flagH = (value & 0x0f) === 0x0f;
2079
+ this.flagPV = value === 0x7f;
2080
+ this.flagN = 0;
2081
+ this._xy(result);
2082
+
2083
+ return 4;
2084
+ }
2085
+
2086
+ /**
2087
+ * Decrement a register with flag updates (except carry)
2088
+ */
2089
+ decReg(reg) {
2090
+ const value = this.registers[reg];
2091
+ const result = (value - 1) & 0xff;
2092
+
2093
+ this.registers[reg] = result;
2094
+ this.flagZ = result === 0;
2095
+ this.flagS = (result & 0x80) !== 0;
2096
+ this.flagH = (value & 0x0f) === 0x00;
2097
+ this.flagPV = value === 0x80;
2098
+ this.flagN = 1;
2099
+ this._xy(result);
2100
+
2101
+ return 4;
2102
+ }
2103
+
2104
+ /**
2105
+ * Add with carry (ADC)
2106
+ */
2107
+ adcA(value) {
2108
+ return this.addA(value, true);
2109
+ }
2110
+
2111
+ /**
2112
+ * Subtract with carry (SBC)
2113
+ */
2114
+ sbcA(value) {
2115
+ const a = this.registers.A;
2116
+ const carry = this.flagC ? 1 : 0;
2117
+ const result = a - value - carry;
2118
+
2119
+ this.flagC = result < 0;
2120
+ this.flagH = (a & 0x0f) - (value & 0x0f) - carry < 0;
2121
+ this.flagN = 1;
2122
+
2123
+ this.registers.A = result & 0xff;
2124
+ const finalResult = result & 0xff;
2125
+ this.flagZ = finalResult === 0;
2126
+ this.flagS = (finalResult & 0x80) !== 0;
2127
+ this.flagPV = ((a ^ value) & (a ^ finalResult) & 0x80) !== 0;
2128
+ this._xy(finalResult);
2129
+
2130
+ return 4;
2131
+ }
2132
+
2133
+ /**
2134
+ * AND operation
2135
+ */
2136
+ andA(value) {
2137
+ this.registers.A = this.registers.A & value;
2138
+ this.flagZ = this.registers.A === 0;
2139
+ this.flagS = (this.registers.A & 0x80) !== 0;
2140
+ this.flagH = true;
2141
+ this.flagPV = this.parity(this.registers.A);
2142
+ this.flagN = 0;
2143
+ this.flagC = false;
2144
+ this._xy(this.registers.A);
2145
+ return 4;
2146
+ }
2147
+
2148
+ /**
2149
+ * OR operation
2150
+ */
2151
+ orA(value) {
2152
+ this.registers.A = this.registers.A | value;
2153
+ this.flagZ = this.registers.A === 0;
2154
+ this.flagS = (this.registers.A & 0x80) !== 0;
2155
+ this.flagH = false;
2156
+ this.flagPV = this.parity(this.registers.A);
2157
+ this.flagN = 0;
2158
+ this.flagC = false;
2159
+ this._xy(this.registers.A);
2160
+ return 4;
2161
+ }
2162
+
2163
+ /**
2164
+ * XOR operation
2165
+ */
2166
+ xorA(value) {
2167
+ this.registers.A = this.registers.A ^ value;
2168
+ this.flagZ = this.registers.A === 0;
2169
+ this.flagS = (this.registers.A & 0x80) !== 0;
2170
+ this.flagH = false;
2171
+ this.flagPV = this.parity(this.registers.A);
2172
+ this.flagN = 0;
2173
+ this.flagC = false;
2174
+ this._xy(this.registers.A);
2175
+ return 4;
2176
+ }
2177
+
2178
+ /**
2179
+ * Calculate parity (even parity)
2180
+ */
2181
+ parity(value) {
2182
+ let p = 0;
2183
+ for (let i = 0; i < 8; i++) {
2184
+ if (value & (1 << i)) p++;
2185
+ }
2186
+ return (p & 1) === 0 ? 1 : 0;
2187
+ }
2188
+
2189
+ /**
2190
+ * Add 16-bit value to HL
2191
+ */
2192
+ addHL(value) {
2193
+ const hl = this.HL;
2194
+ const result = hl + value;
2195
+ this.flagC = result > 0xffff;
2196
+ this.flagH = (hl & 0x0fff) + (value & 0x0fff) > 0x0fff;
2197
+ this.flagN = false;
2198
+ this.HL = result & 0xffff;
2199
+ this.wz = (hl + 1) & 0xffff;
2200
+ this._xy(this.HL >> 8); // 16-bit ops take X/Y from the high byte
2201
+ return 4;
2202
+ }
2203
+
2204
+ // CB prefix helper methods
2205
+ rlcReg(reg) {
2206
+ const value = this.registers[reg];
2207
+ this.flagC = (value & 0x80) !== 0;
2208
+ this.registers[reg] = ((value << 1) | (value >> 7)) & 0xff;
2209
+ this.flagZ = this.registers[reg] === 0;
2210
+ this.flagS = (this.registers[reg] & 0x80) !== 0;
2211
+ this.flagPV = this.parity(this.registers[reg]);
2212
+ this.flagH = false;
2213
+ this.flagN = false;
2214
+ this._xy(this.registers[reg]);
2215
+ return 8;
2216
+ }
2217
+
2218
+ rlcMem(addr) {
2219
+ const value = this.readMemory(addr);
2220
+ this.flagC = (value & 0x80) !== 0;
2221
+ const result = ((value << 1) | (value >> 7)) & 0xff;
2222
+ this.writeMemory(addr, result);
2223
+ this.flagZ = result === 0;
2224
+ this.flagS = (result & 0x80) !== 0;
2225
+ this.flagPV = this.parity(result);
2226
+ this.flagH = false;
2227
+ this.flagN = false;
2228
+ this._xy(result);
2229
+ return 15;
2230
+ }
2231
+
2232
+ rrcReg(reg) {
2233
+ const value = this.registers[reg];
2234
+ this.flagC = (value & 0x01) !== 0;
2235
+ this.registers[reg] = ((value >> 1) | (value << 7)) & 0xff;
2236
+ this.flagZ = this.registers[reg] === 0;
2237
+ this.flagS = (this.registers[reg] & 0x80) !== 0;
2238
+ this.flagPV = this.parity(this.registers[reg]);
2239
+ this.flagH = false;
2240
+ this.flagN = false;
2241
+ this._xy(this.registers[reg]);
2242
+ return 8;
2243
+ }
2244
+
2245
+ rrcMem(addr) {
2246
+ const value = this.readMemory(addr);
2247
+ this.flagC = (value & 0x01) !== 0;
2248
+ const result = ((value >> 1) | (value << 7)) & 0xff;
2249
+ this.writeMemory(addr, result);
2250
+ this.flagZ = result === 0;
2251
+ this.flagS = (result & 0x80) !== 0;
2252
+ this.flagPV = this.parity(result);
2253
+ this.flagH = false;
2254
+ this.flagN = false;
2255
+ this._xy(result);
2256
+ return 15;
2257
+ }
2258
+
2259
+ rlReg(reg) {
2260
+ const value = this.registers[reg];
2261
+ const carry = this.flagC ? 1 : 0;
2262
+ this.flagC = (value & 0x80) !== 0;
2263
+ this.registers[reg] = ((value << 1) | carry) & 0xff;
2264
+ this.flagZ = this.registers[reg] === 0;
2265
+ this.flagS = (this.registers[reg] & 0x80) !== 0;
2266
+ this.flagPV = this.parity(this.registers[reg]);
2267
+ this.flagH = false;
2268
+ this.flagN = false;
2269
+ this._xy(this.registers[reg]);
2270
+ return 8;
2271
+ }
2272
+
2273
+ rlMem(addr) {
2274
+ const value = this.readMemory(addr);
2275
+ const carry = this.flagC ? 1 : 0;
2276
+ this.flagC = (value & 0x80) !== 0;
2277
+ const result = ((value << 1) | carry) & 0xff;
2278
+ this.writeMemory(addr, result);
2279
+ this.flagZ = result === 0;
2280
+ this.flagS = (result & 0x80) !== 0;
2281
+ this.flagPV = this.parity(result);
2282
+ this.flagH = false;
2283
+ this.flagN = false;
2284
+ this._xy(result);
2285
+ return 15;
2286
+ }
2287
+
2288
+ rrReg(reg) {
2289
+ const value = this.registers[reg];
2290
+ const carry = this.flagC ? 0x80 : 0;
2291
+ this.flagC = (value & 0x01) !== 0;
2292
+ this.registers[reg] = ((value >> 1) | carry) & 0xff;
2293
+ this.flagZ = this.registers[reg] === 0;
2294
+ this.flagS = (this.registers[reg] & 0x80) !== 0;
2295
+ this.flagPV = this.parity(this.registers[reg]);
2296
+ this.flagH = false;
2297
+ this.flagN = false;
2298
+ this._xy(this.registers[reg]);
2299
+ return 8;
2300
+ }
2301
+
2302
+ rrMem(addr) {
2303
+ const value = this.readMemory(addr);
2304
+ const carry = this.flagC ? 0x80 : 0;
2305
+ this.flagC = (value & 0x01) !== 0;
2306
+ const result = ((value >> 1) | carry) & 0xff;
2307
+ this.writeMemory(addr, result);
2308
+ this.flagZ = result === 0;
2309
+ this.flagS = (result & 0x80) !== 0;
2310
+ this.flagPV = this.parity(result);
2311
+ this.flagH = false;
2312
+ this.flagN = false;
2313
+ this._xy(result);
2314
+ return 15;
2315
+ }
2316
+
2317
+ slaReg(reg) {
2318
+ const value = this.registers[reg];
2319
+ this.flagC = (value & 0x80) !== 0;
2320
+ this.registers[reg] = (value << 1) & 0xff;
2321
+ this.flagZ = this.registers[reg] === 0;
2322
+ this.flagS = (this.registers[reg] & 0x80) !== 0;
2323
+ this.flagPV = this.parity(this.registers[reg]);
2324
+ this.flagH = false;
2325
+ this.flagN = false;
2326
+ this._xy(this.registers[reg]);
2327
+ return 8;
2328
+ }
2329
+
2330
+ slaMem(addr) {
2331
+ const value = this.readMemory(addr);
2332
+ this.flagC = (value & 0x80) !== 0;
2333
+ const result = (value << 1) & 0xff;
2334
+ this.writeMemory(addr, result);
2335
+ this.flagZ = result === 0;
2336
+ this.flagS = (result & 0x80) !== 0;
2337
+ this.flagPV = this.parity(result);
2338
+ this.flagH = false;
2339
+ this.flagN = false;
2340
+ this._xy(result);
2341
+ return 15;
2342
+ }
2343
+
2344
+ sraReg(reg) {
2345
+ const value = this.registers[reg];
2346
+ this.flagC = (value & 0x01) !== 0;
2347
+ this.registers[reg] = ((value >> 1) | (value & 0x80)) & 0xff;
2348
+ this.flagZ = this.registers[reg] === 0;
2349
+ this.flagS = (this.registers[reg] & 0x80) !== 0;
2350
+ this.flagPV = this.parity(this.registers[reg]);
2351
+ this.flagH = false;
2352
+ this.flagN = false;
2353
+ this._xy(this.registers[reg]);
2354
+ return 8;
2355
+ }
2356
+
2357
+ sraMem(addr) {
2358
+ const value = this.readMemory(addr);
2359
+ this.flagC = (value & 0x01) !== 0;
2360
+ const result = ((value >> 1) | (value & 0x80)) & 0xff;
2361
+ this.writeMemory(addr, result);
2362
+ this.flagZ = result === 0;
2363
+ this.flagS = (result & 0x80) !== 0;
2364
+ this.flagPV = this.parity(result);
2365
+ this.flagH = false;
2366
+ this.flagN = false;
2367
+ this._xy(result);
2368
+ return 15;
2369
+ }
2370
+
2371
+ sllReg(reg) {
2372
+ const value = this.registers[reg];
2373
+ this.flagC = (value & 0x80) !== 0;
2374
+ this.registers[reg] = ((value << 1) | 1) & 0xff;
2375
+ this.flagZ = this.registers[reg] === 0;
2376
+ this.flagS = (this.registers[reg] & 0x80) !== 0;
2377
+ this.flagPV = this.parity(this.registers[reg]);
2378
+ this.flagH = false;
2379
+ this.flagN = false;
2380
+ this._xy(this.registers[reg]);
2381
+ return 8;
2382
+ }
2383
+
2384
+ sllMem(addr) {
2385
+ const value = this.readMemory(addr);
2386
+ this.flagC = (value & 0x80) !== 0;
2387
+ const result = ((value << 1) | 1) & 0xff;
2388
+ this.writeMemory(addr, result);
2389
+ this.flagZ = result === 0;
2390
+ this.flagS = (result & 0x80) !== 0;
2391
+ this.flagPV = this.parity(result);
2392
+ this.flagH = false;
2393
+ this.flagN = false;
2394
+ this._xy(result);
2395
+ return 15;
2396
+ }
2397
+
2398
+ srlReg(reg) {
2399
+ const value = this.registers[reg];
2400
+ this.flagC = (value & 0x01) !== 0;
2401
+ this.registers[reg] = (value >> 1) & 0xff;
2402
+ this.flagZ = this.registers[reg] === 0;
2403
+ this.flagS = false;
2404
+ this.flagPV = this.parity(this.registers[reg]);
2405
+ this.flagH = false;
2406
+ this.flagN = false;
2407
+ this._xy(this.registers[reg]);
2408
+ return 8;
2409
+ }
2410
+
2411
+ srlMem(addr) {
2412
+ const value = this.readMemory(addr);
2413
+ this.flagC = (value & 0x01) !== 0;
2414
+ const result = (value >> 1) & 0xff;
2415
+ this.writeMemory(addr, result);
2416
+ this.flagZ = result === 0;
2417
+ this.flagS = false;
2418
+ this.flagPV = this.parity(result);
2419
+ this.flagH = false;
2420
+ this.flagN = false;
2421
+ this._xy(result);
2422
+ return 15;
2423
+ }
2424
+
2425
+ bitReg(reg, bit) {
2426
+ const value = this.registers[reg];
2427
+ const bitValue = (value >> bit) & 1;
2428
+ this.flagZ = bitValue === 0;
2429
+ this.flagS = bit === 7 ? bitValue : false;
2430
+ this.flagH = true;
2431
+ this.flagPV = bitValue === 0;
2432
+ this.flagN = false;
2433
+ this._xy(value);
2434
+ return 8;
2435
+ }
2436
+
2437
+ bitMem(addr, bit) {
2438
+ const value = this.readMemory(addr);
2439
+ const bitValue = (value >> bit) & 1;
2440
+ this.flagZ = bitValue === 0;
2441
+ this.flagS = bit === 7 ? bitValue : false;
2442
+ this.flagH = true;
2443
+ this.flagPV = bitValue === 0;
2444
+ this.flagN = false;
2445
+ // BIT n,(HL) exposes MEMPTR's high byte; the DD/FD CB caller sets
2446
+ // wz = IX+d first, which makes the indexed form come out right too.
2447
+ this._xy(this.wz >> 8);
2448
+ return 12;
2449
+ }
2450
+
2451
+ resReg(reg, bit) {
2452
+ this.registers[reg] = this.registers[reg] & ~(1 << bit);
2453
+ return 8;
2454
+ }
2455
+
2456
+ resMem(addr, bit) {
2457
+ const value = this.readMemory(addr);
2458
+ this.writeMemory(addr, value & ~(1 << bit));
2459
+ return 15;
2460
+ }
2461
+
2462
+ setReg(reg, bit) {
2463
+ this.registers[reg] = this.registers[reg] | (1 << bit);
2464
+ return 8;
2465
+ }
2466
+
2467
+ setMem(addr, bit) {
2468
+ const value = this.readMemory(addr);
2469
+ this.writeMemory(addr, value | (1 << bit));
2470
+ return 15;
2471
+ }
2472
+
2473
+ // DD/FD prefix helper methods
2474
+ executeIndexReg(opcode, reg) {
2475
+ // Most DD/FD instructions are identical to HL instructions but use IX/IY
2476
+ // Special handling for indexed operations
2477
+ const isIX = reg === "IX";
2478
+ const indexReg = isIX ? this.IX : this.IY;
2479
+
2480
+ switch (opcode) {
2481
+ // LD IX/IY, nn (0x21)
2482
+ case 0x21: {
2483
+ const low = this.readMemory(this.registers.PC);
2484
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
2485
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
2486
+ if (isIX) {
2487
+ this.IX = (high << 8) | low;
2488
+ } else {
2489
+ this.IY = (high << 8) | low;
2490
+ }
2491
+ return 14;
2492
+ }
2493
+
2494
+ // ADD IX/IY, BC (0x09)
2495
+ case 0x09:
2496
+ return this.addIndexReg(indexReg, this.BC, isIX);
2497
+ // ADD IX/IY, DE (0x19)
2498
+ case 0x19:
2499
+ return this.addIndexReg(indexReg, this.DE, isIX);
2500
+ // ADD IX/IY, IX/IY (0x29)
2501
+ case 0x29:
2502
+ return this.addIndexReg(indexReg, indexReg, isIX);
2503
+ // ADD IX/IY, SP (0x39)
2504
+ case 0x39:
2505
+ return this.addIndexReg(indexReg, this.registers.SP, isIX);
2506
+
2507
+ // INC IX/IY (0x23)
2508
+ case 0x23:
2509
+ if (isIX) {
2510
+ this.IX = (this.IX + 1) & 0xffff;
2511
+ } else {
2512
+ this.IY = (this.IY + 1) & 0xffff;
2513
+ }
2514
+ return 10;
2515
+
2516
+ // DEC IX/IY (0x2B)
2517
+ case 0x2b:
2518
+ if (isIX) {
2519
+ this.IX = (this.IX - 1) & 0xffff;
2520
+ } else {
2521
+ this.IY = (this.IY - 1) & 0xffff;
2522
+ }
2523
+ return 10;
2524
+
2525
+ // PUSH IX/IY (0xE5)
2526
+ case 0xe5: {
2527
+ const high = isIX ? this.registers.IXH : this.registers.IYH;
2528
+ const low = isIX ? this.registers.IXL : this.registers.IYL;
2529
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
2530
+ this.writeMemory(this.registers.SP, high);
2531
+ this.registers.SP = (this.registers.SP - 1) & 0xffff;
2532
+ this.writeMemory(this.registers.SP, low);
2533
+ return 15;
2534
+ }
2535
+
2536
+ // POP IX/IY (0xE1)
2537
+ case 0xe1: {
2538
+ const low = this.readMemory(this.registers.SP);
2539
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
2540
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
2541
+ if (isIX) {
2542
+ this.registers.IXL = low;
2543
+ this.registers.IXH = high;
2544
+ } else {
2545
+ this.registers.IYL = low;
2546
+ this.registers.IYH = high;
2547
+ }
2548
+ return 14;
2549
+ }
2550
+
2551
+ // LD (nn), IX/IY (0x22)
2552
+ case 0x22: {
2553
+ const low = this.readMemory(this.registers.PC);
2554
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
2555
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
2556
+ const addr = (high << 8) | low;
2557
+ this.wz = (addr + 1) & 0xffff;
2558
+ const regLow = isIX ? this.registers.IXL : this.registers.IYL;
2559
+ const regHigh = isIX ? this.registers.IXH : this.registers.IYH;
2560
+ this.writeMemory(addr, regLow);
2561
+ this.writeMemory((addr + 1) & 0xffff, regHigh);
2562
+ return 20;
2563
+ }
2564
+
2565
+ // LD IX/IY, (nn) (0x2A)
2566
+ case 0x2a: {
2567
+ const low = this.readMemory(this.registers.PC);
2568
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
2569
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
2570
+ const addr = (high << 8) | low;
2571
+ this.wz = (addr + 1) & 0xffff;
2572
+ const regLow = this.readMemory(addr);
2573
+ const regHigh = this.readMemory((addr + 1) & 0xffff);
2574
+ if (isIX) {
2575
+ this.registers.IXL = regLow;
2576
+ this.registers.IXH = regHigh;
2577
+ } else {
2578
+ this.registers.IYL = regLow;
2579
+ this.registers.IYH = regHigh;
2580
+ }
2581
+ return 20;
2582
+ }
2583
+
2584
+ // JP (IX/IY) (0xE9)
2585
+ case 0xe9:
2586
+ this.registers.PC = indexReg;
2587
+ return 8;
2588
+
2589
+ // LD SP, IX/IY (0xF9)
2590
+ case 0xf9:
2591
+ this.registers.SP = indexReg;
2592
+ return 10;
2593
+
2594
+ default: {
2595
+ // Opcodes whose (HL) operand becomes (IX+d)/(IY+d). Unlike the HL
2596
+ // forms, these consume a signed displacement byte, and register
2597
+ // names in the opcode keep meaning the REAL registers (H stays H).
2598
+ const indexedAddr = () => {
2599
+ const disp = this.readMemory(this.registers.PC);
2600
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
2601
+ return (this.wz = (indexReg + (disp & 0x80 ? disp - 256 : disp)) & 0xffff);
2602
+ };
2603
+ const regNames = ["B", "C", "D", "E", "H", "L", null, "A"];
2604
+
2605
+ // LD r,(IX+d): 01 rrr 110, excluding HALT's slot
2606
+ if ((opcode & 0xc7) === 0x46 && opcode !== 0x76) {
2607
+ const dest = regNames[(opcode >> 3) & 0x07];
2608
+ this.registers[dest] = this.readMemory(indexedAddr());
2609
+ return 19;
2610
+ }
2611
+
2612
+ // LD (IX+d),r: 01 110 rrr, excluding HALT
2613
+ if ((opcode & 0xf8) === 0x70 && opcode !== 0x76) {
2614
+ const src = regNames[opcode & 0x07];
2615
+ this.writeMemory(indexedAddr(), this.registers[src]);
2616
+ return 19;
2617
+ }
2618
+
2619
+ switch (opcode) {
2620
+ case 0x34: {
2621
+ // INC (IX+d)
2622
+ const addr = indexedAddr();
2623
+ const value = this.readMemory(addr);
2624
+ const result = (value + 1) & 0xff;
2625
+ this.writeMemory(addr, result);
2626
+ this.flagZ = result === 0;
2627
+ this.flagS = (result & 0x80) !== 0;
2628
+ this.flagH = (value & 0x0f) === 0x0f;
2629
+ this.flagPV = value === 0x7f;
2630
+ this.flagN = 0;
2631
+ this._xy(result);
2632
+ return 23;
2633
+ }
2634
+ case 0x35: {
2635
+ // DEC (IX+d)
2636
+ const addr = indexedAddr();
2637
+ const value = this.readMemory(addr);
2638
+ const result = (value - 1) & 0xff;
2639
+ this.writeMemory(addr, result);
2640
+ this.flagZ = result === 0;
2641
+ this.flagS = (result & 0x80) !== 0;
2642
+ this.flagH = (value & 0x0f) === 0x00;
2643
+ this.flagPV = value === 0x80;
2644
+ this.flagN = 1;
2645
+ this._xy(result);
2646
+ return 23;
2647
+ }
2648
+ case 0x36: {
2649
+ // LD (IX+d),n — displacement byte precedes the immediate
2650
+ const addr = indexedAddr();
2651
+ const value = this.readMemory(this.registers.PC);
2652
+ this.registers.PC = (this.registers.PC + 1) & 0xffff;
2653
+ this.writeMemory(addr, value);
2654
+ return 19;
2655
+ }
2656
+ case 0x86:
2657
+ this.addA(this.readMemory(indexedAddr()));
2658
+ return 19;
2659
+ case 0x8e:
2660
+ this.adcA(this.readMemory(indexedAddr()));
2661
+ return 19;
2662
+ case 0x96:
2663
+ this.subA(this.readMemory(indexedAddr()));
2664
+ return 19;
2665
+ case 0x9e:
2666
+ this.sbcA(this.readMemory(indexedAddr()));
2667
+ return 19;
2668
+ case 0xa6:
2669
+ this.andA(this.readMemory(indexedAddr()));
2670
+ return 19;
2671
+ case 0xae:
2672
+ this.xorA(this.readMemory(indexedAddr()));
2673
+ return 19;
2674
+ case 0xb6:
2675
+ this.orA(this.readMemory(indexedAddr()));
2676
+ return 19;
2677
+ case 0xbe:
2678
+ this.subA(this.readMemory(indexedAddr()), true);
2679
+ return 19;
2680
+ }
2681
+
2682
+ // Remaining DD/FD opcodes mirror their HL forms with IX/IY
2683
+ // substituted and take no displacement (INC IXH, ADD A,IXL, ...):
2684
+ // the HL-swap delegation is faithful for these.
2685
+ if (this.opcodeHandlers[opcode]) {
2686
+ // EX DE,HL and EXX ignore the prefix
2687
+ if (opcode === 0xeb || opcode === 0xd9) {
2688
+ return this.opcodeHandlers[opcode].call(this) + 4;
2689
+ }
2690
+ const tempHL = this.HL;
2691
+ this.HL = indexReg;
2692
+ const cycles = this.opcodeHandlers[opcode].call(this);
2693
+ if (isIX) {
2694
+ this.IX = this.HL;
2695
+ } else {
2696
+ this.IY = this.HL;
2697
+ }
2698
+ this.HL = tempHL;
2699
+ return cycles + 4;
2700
+ }
2701
+
2702
+ return this.reportUnimplemented(opcode, isIX ? "0xDD " : "0xFD ");
2703
+ }
2704
+ }
2705
+ }
2706
+
2707
+ addIndexReg(indexReg, value, isIX) {
2708
+ const result = indexReg + value;
2709
+ this.flagC = result > 0xffff;
2710
+ this.flagH = (indexReg & 0x0fff) + (value & 0x0fff) > 0x0fff;
2711
+ this.flagN = false;
2712
+ if (isIX) {
2713
+ this.IX = result & 0xffff;
2714
+ } else {
2715
+ this.IY = result & 0xffff;
2716
+ }
2717
+ this.wz = (indexReg + 1) & 0xffff;
2718
+ this._xy((result & 0xffff) >> 8);
2719
+ return 15;
2720
+ }
2721
+
2722
+ executeIndexedCB(addr, cbOpcode) {
2723
+ const reg = cbOpcode & 0x07;
2724
+ const base = cbOpcode & 0xf8;
2725
+ this.wz = addr & 0xffff; // MEMPTR = IX+d (feeds BIT's X/Y)
2726
+
2727
+ // BIT only reads: 20 T-states, never touches a register
2728
+ if (base >= 0x40 && base <= 0x78) {
2729
+ this.bitMem(addr, (base >> 3) & 0x07);
2730
+ return 20;
2731
+ }
2732
+
2733
+ switch (base) {
2734
+ case 0x00:
2735
+ this.rlcMem(addr);
2736
+ break;
2737
+ case 0x08:
2738
+ this.rrcMem(addr);
2739
+ break;
2740
+ case 0x10:
2741
+ this.rlMem(addr);
2742
+ break;
2743
+ case 0x18:
2744
+ this.rrMem(addr);
2745
+ break;
2746
+ case 0x20:
2747
+ this.slaMem(addr);
2748
+ break;
2749
+ case 0x28:
2750
+ this.sraMem(addr);
2751
+ break;
2752
+ case 0x30:
2753
+ this.sllMem(addr);
2754
+ break;
2755
+ case 0x38:
2756
+ this.srlMem(addr);
2757
+ break;
2758
+ default:
2759
+ // 0x80-0xF8: RES/SET
2760
+ if (base & 0x40) {
2761
+ this.setMem(addr, (base >> 3) & 0x07);
2762
+ } else {
2763
+ this.resMem(addr, (base >> 3) & 0x07);
2764
+ }
2765
+ break;
2766
+ }
2767
+
2768
+ // Undocumented: every writing DD/FD CB form also copies the result
2769
+ // into the register named by the low 3 bits (110 = memory only).
2770
+ if (reg !== 6) {
2771
+ this.registers[INDEXED_CB_COPY_REGS[reg]] = this.readMemory(addr);
2772
+ }
2773
+ return 23;
2774
+ }
2775
+
2776
+ // Block Transfer Instructions
2777
+ /**
2778
+ * LDI - Load and Increment
2779
+ * Copies byte from (HL) to (DE), increments HL and DE, decrements BC
2780
+ * Flags: H=0, N=0, PV=1 if BC-1 != 0, else 0
2781
+ */
2782
+ ldi() {
2783
+ const value = this.readMemory(this.HL);
2784
+ this.writeMemory(this.DE, value);
2785
+ this.HL = (this.HL + 1) & 0xffff;
2786
+ this.DE = (this.DE + 1) & 0xffff;
2787
+ this.BC = (this.BC - 1) & 0xffff;
2788
+
2789
+ // Flag updates
2790
+ this.flagH = false;
2791
+ this.flagN = false;
2792
+ this.flagPV = this.BC !== 0;
2793
+ this._xyBlock((value + this.registers.A) & 0xff);
2794
+
2795
+ return 16;
2796
+ }
2797
+
2798
+ /**
2799
+ * LDIR - Load, Increment, Repeat
2800
+ * Repeats LDI until BC = 0
2801
+ */
2802
+ ldir() {
2803
+ const cycles = this.ldi();
2804
+ if (this.BC !== 0) {
2805
+ // Repeat: decrement PC by 2 to re-execute instruction
2806
+ this.registers.PC = (this.registers.PC - 2) & 0xffff;
2807
+ this.wz = (this.registers.PC + 1) & 0xffff;
2808
+ return cycles + 5; // Extra cycles for repeat
2809
+ }
2810
+ return cycles;
2811
+ }
2812
+
2813
+ /**
2814
+ * LDD - Load and Decrement
2815
+ * Copies byte from (HL) to (DE), decrements HL and DE, decrements BC
2816
+ */
2817
+ ldd() {
2818
+ const value = this.readMemory(this.HL);
2819
+ this.writeMemory(this.DE, value);
2820
+ this.HL = (this.HL - 1) & 0xffff;
2821
+ this.DE = (this.DE - 1) & 0xffff;
2822
+ this.BC = (this.BC - 1) & 0xffff;
2823
+
2824
+ // Flag updates
2825
+ this.flagH = false;
2826
+ this.flagN = false;
2827
+ this.flagPV = this.BC !== 0;
2828
+ this._xyBlock((value + this.registers.A) & 0xff);
2829
+
2830
+ return 16;
2831
+ }
2832
+
2833
+ /**
2834
+ * LDDR - Load, Decrement, Repeat
2835
+ * Repeats LDD until BC = 0
2836
+ */
2837
+ lddr() {
2838
+ const cycles = this.ldd();
2839
+ if (this.BC !== 0) {
2840
+ this.registers.PC = (this.registers.PC - 2) & 0xffff;
2841
+ this.wz = (this.registers.PC + 1) & 0xffff;
2842
+ return cycles + 5;
2843
+ }
2844
+ return cycles;
2845
+ }
2846
+
2847
+ // Block Compare Instructions
2848
+ /**
2849
+ * CPI - Compare and Increment
2850
+ * Compares A with (HL), increments HL, decrements BC
2851
+ * Flags: Z=1 if A==(HL), H from subtraction, N=1, PV=1 if BC-1 != 0
2852
+ */
2853
+ cpi() {
2854
+ const value = this.readMemory(this.HL);
2855
+ const a = this.registers.A;
2856
+ const result = (a - value) & 0xff;
2857
+
2858
+ this.HL = (this.HL + 1) & 0xffff;
2859
+ this.BC = (this.BC - 1) & 0xffff;
2860
+ this.wz = (this.wz + 1) & 0xffff;
2861
+
2862
+ // Flag updates
2863
+ this.flagZ = result === 0;
2864
+ this.flagS = (result & 0x80) !== 0;
2865
+ this.flagH = (a & 0x0f) - (value & 0x0f) < 0;
2866
+ this.flagPV = this.BC !== 0;
2867
+ this.flagN = 1;
2868
+ this._xyBlock((result - (this.flagH ? 1 : 0)) & 0xff);
2869
+
2870
+ return 16;
2871
+ }
2872
+
2873
+ /**
2874
+ * CPIR - Compare, Increment, Repeat
2875
+ * Repeats CPI until BC = 0 or match found (Z=1)
2876
+ */
2877
+ cpir() {
2878
+ const cycles = this.cpi();
2879
+ if (this.BC !== 0 && !this.flagZ) {
2880
+ this.registers.PC = (this.registers.PC - 2) & 0xffff;
2881
+ this.wz = (this.registers.PC + 1) & 0xffff;
2882
+ return cycles + 5;
2883
+ }
2884
+ return cycles;
2885
+ }
2886
+
2887
+ /**
2888
+ * CPD - Compare and Decrement
2889
+ * Compares A with (HL), decrements HL, decrements BC
2890
+ */
2891
+ cpd() {
2892
+ const value = this.readMemory(this.HL);
2893
+ const a = this.registers.A;
2894
+ const result = (a - value) & 0xff;
2895
+
2896
+ this.HL = (this.HL - 1) & 0xffff;
2897
+ this.BC = (this.BC - 1) & 0xffff;
2898
+ this.wz = (this.wz - 1) & 0xffff;
2899
+
2900
+ // Flag updates
2901
+ this.flagZ = result === 0;
2902
+ this.flagS = (result & 0x80) !== 0;
2903
+ this.flagH = (a & 0x0f) - (value & 0x0f) < 0;
2904
+ this.flagPV = this.BC !== 0;
2905
+ this.flagN = 1;
2906
+ this._xyBlock((result - (this.flagH ? 1 : 0)) & 0xff);
2907
+
2908
+ return 16;
2909
+ }
2910
+
2911
+ /**
2912
+ * CPDR - Compare, Decrement, Repeat
2913
+ * Repeats CPD until BC = 0 or match found (Z=1)
2914
+ */
2915
+ cpdr() {
2916
+ const cycles = this.cpd();
2917
+ if (this.BC !== 0 && !this.flagZ) {
2918
+ this.registers.PC = (this.registers.PC - 2) & 0xffff;
2919
+ this.wz = (this.registers.PC + 1) & 0xffff;
2920
+ return cycles + 5;
2921
+ }
2922
+ return cycles;
2923
+ }
2924
+
2925
+ // Extended Load Instructions
2926
+ /**
2927
+ * LD BC, (nn) - Load BC from memory address
2928
+ */
2929
+ ldBCnn() {
2930
+ const low = this.readMemory(this.registers.PC);
2931
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
2932
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
2933
+ const addr = (high << 8) | low;
2934
+ this.wz = (addr + 1) & 0xffff;
2935
+ const valueLow = this.readMemory(addr);
2936
+ const valueHigh = this.readMemory((addr + 1) & 0xffff);
2937
+ this.BC = (valueHigh << 8) | valueLow;
2938
+ return 20;
2939
+ }
2940
+
2941
+ /**
2942
+ * LD DE, (nn) - Load DE from memory address
2943
+ */
2944
+ ldDEnn() {
2945
+ const low = this.readMemory(this.registers.PC);
2946
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
2947
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
2948
+ const addr = (high << 8) | low;
2949
+ this.wz = (addr + 1) & 0xffff;
2950
+ const valueLow = this.readMemory(addr);
2951
+ const valueHigh = this.readMemory((addr + 1) & 0xffff);
2952
+ this.DE = (valueHigh << 8) | valueLow;
2953
+ return 20;
2954
+ }
2955
+
2956
+ /**
2957
+ * LD HL, (nn) - Load HL from memory address
2958
+ */
2959
+ ldHLnn() {
2960
+ const low = this.readMemory(this.registers.PC);
2961
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
2962
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
2963
+ const addr = (high << 8) | low;
2964
+ this.wz = (addr + 1) & 0xffff;
2965
+ const valueLow = this.readMemory(addr);
2966
+ const valueHigh = this.readMemory((addr + 1) & 0xffff);
2967
+ this.HL = (valueHigh << 8) | valueLow;
2968
+ return 20;
2969
+ }
2970
+
2971
+ /**
2972
+ * LD SP, (nn) - Load SP from memory address
2973
+ */
2974
+ ldSPnn() {
2975
+ const low = this.readMemory(this.registers.PC);
2976
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
2977
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
2978
+ const addr = (high << 8) | low;
2979
+ this.wz = (addr + 1) & 0xffff;
2980
+ const valueLow = this.readMemory(addr);
2981
+ const valueHigh = this.readMemory((addr + 1) & 0xffff);
2982
+ this.registers.SP = (valueHigh << 8) | valueLow;
2983
+ return 20;
2984
+ }
2985
+
2986
+ /**
2987
+ * LD (nn), BC - Store BC to memory address
2988
+ */
2989
+ ldnnBC() {
2990
+ const low = this.readMemory(this.registers.PC);
2991
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
2992
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
2993
+ const addr = (high << 8) | low;
2994
+ this.wz = (addr + 1) & 0xffff;
2995
+ this.writeMemory(addr, this.registers.C);
2996
+ this.writeMemory((addr + 1) & 0xffff, this.registers.B);
2997
+ return 20;
2998
+ }
2999
+
3000
+ /**
3001
+ * LD (nn), DE - Store DE to memory address
3002
+ */
3003
+ ldnnDE() {
3004
+ const low = this.readMemory(this.registers.PC);
3005
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
3006
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
3007
+ const addr = (high << 8) | low;
3008
+ this.wz = (addr + 1) & 0xffff;
3009
+ this.writeMemory(addr, this.registers.E);
3010
+ this.writeMemory((addr + 1) & 0xffff, this.registers.D);
3011
+ return 20;
3012
+ }
3013
+
3014
+ /**
3015
+ * LD (nn), HL - Store HL to memory address
3016
+ */
3017
+ ldnnHL() {
3018
+ const low = this.readMemory(this.registers.PC);
3019
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
3020
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
3021
+ const addr = (high << 8) | low;
3022
+ this.wz = (addr + 1) & 0xffff;
3023
+ this.writeMemory(addr, this.registers.L);
3024
+ this.writeMemory((addr + 1) & 0xffff, this.registers.H);
3025
+ return 20;
3026
+ }
3027
+
3028
+ /**
3029
+ * LD (nn), SP - Store SP to memory address
3030
+ */
3031
+ ldnnSP() {
3032
+ const low = this.readMemory(this.registers.PC);
3033
+ const high = this.readMemory((this.registers.PC + 1) & 0xffff);
3034
+ this.registers.PC = (this.registers.PC + 2) & 0xffff;
3035
+ const addr = (high << 8) | low;
3036
+ this.wz = (addr + 1) & 0xffff;
3037
+ this.writeMemory(addr, this.registers.SP & 0xff);
3038
+ this.writeMemory((addr + 1) & 0xffff, (this.registers.SP >> 8) & 0xff);
3039
+ return 20;
3040
+ }
3041
+
3042
+ // Extended Arithmetic Instructions
3043
+ /**
3044
+ * ADC HL, rr - Add with carry to HL
3045
+ */
3046
+ adcHL(value) {
3047
+ const hl = this.HL;
3048
+ const carry = this.flagC ? 1 : 0;
3049
+ const result = hl + value + carry;
3050
+
3051
+ this.flagC = result > 0xffff;
3052
+ this.flagH = (hl & 0x0fff) + (value & 0x0fff) + carry > 0x0fff;
3053
+ this.flagN = false;
3054
+ this.flagPV = ((hl ^ value ^ 0xffff) & (hl ^ result) & 0x8000) !== 0;
3055
+ this.HL = result & 0xffff;
3056
+ this.flagS = (this.HL & 0x8000) !== 0;
3057
+ this.flagZ = this.HL === 0;
3058
+ this.wz = (hl + 1) & 0xffff;
3059
+ this._xy(this.HL >> 8);
3060
+
3061
+ return 15;
3062
+ }
3063
+
3064
+ /**
3065
+ * SBC HL, rr - Subtract with borrow from HL
3066
+ */
3067
+ sbcHL(value) {
3068
+ const hl = this.HL;
3069
+ const borrow = this.flagC ? 1 : 0;
3070
+ const result = hl - value - borrow;
3071
+
3072
+ this.flagC = result < 0;
3073
+ this.flagH = (hl & 0x0fff) - (value & 0x0fff) - borrow < 0;
3074
+ this.flagN = true;
3075
+ this.flagPV = ((hl ^ value) & (hl ^ result) & 0x8000) !== 0;
3076
+ this.HL = result & 0xffff;
3077
+ this.flagS = (this.HL & 0x8000) !== 0;
3078
+ this.flagZ = this.HL === 0;
3079
+ this.wz = (hl + 1) & 0xffff;
3080
+ this._xy(this.HL >> 8);
3081
+
3082
+ return 15;
3083
+ }
3084
+
3085
+ /**
3086
+ * NEG - Negate accumulator (two's complement)
3087
+ */
3088
+ neg() {
3089
+ const a = this.registers.A;
3090
+ const result = (0 - a) & 0xff;
3091
+
3092
+ this.registers.A = result;
3093
+ this.flagC = a !== 0;
3094
+ this.flagH = (a & 0x0f) !== 0;
3095
+ this.flagN = true;
3096
+ this.flagPV = a === 0x80;
3097
+ this.flagZ = result === 0;
3098
+ this.flagS = (result & 0x80) !== 0;
3099
+ this._xy(result);
3100
+
3101
+ return 8;
3102
+ }
3103
+
3104
+ // Extended I/O Instructions
3105
+ /**
3106
+ * IN r, (C) - Input from port C to register
3107
+ * Register mapping: 0=B, 1=C, 2=D, 3=E, 4=H, 5=L, 6=(HL), 7=A
3108
+ */
3109
+ inrC(regIndex) {
3110
+ this.wz = (this.BC + 1) & 0xffff;
3111
+ const port = this.registers.C;
3112
+ const value = this.readPort(port);
3113
+
3114
+ const regs = ["B", "C", "D", "E", "H", "L", null, "A"];
3115
+ if (regIndex !== 6) this.registers[regs[regIndex]] = value; // 6 is IN F,(C): flags only
3116
+
3117
+ // Flag updates
3118
+ this.flagZ = value === 0;
3119
+ this.flagS = (value & 0x80) !== 0;
3120
+ this.flagH = false;
3121
+ this.flagPV = this.parity(value);
3122
+ this.flagN = false;
3123
+ this._xy(value);
3124
+
3125
+ return 12;
3126
+ }
3127
+
3128
+ /**
3129
+ * OUT (C), r - Output register to port C
3130
+ */
3131
+ outCr(regIndex) {
3132
+ this.wz = (this.BC + 1) & 0xffff;
3133
+ const port = this.registers.C;
3134
+ const regs = ["B", "C", "D", "E", "H", "L", null, "A"];
3135
+ // 6 is OUT (C),0; an NMOS Z80, as in the Model I and III, sends 0 (CMOS parts send FFH)
3136
+ const value = regIndex === 6 ? 0 : this.registers[regs[regIndex]];
3137
+ this.writePort(port, value);
3138
+ return 12;
3139
+ }
3140
+
3141
+ /**
3142
+ * Block I/O flags (Young §4.3): S, Z, 5, 3 from B after it counts down; N from bit 7 of the
3143
+ * byte moved; H and C from the carry out of k; P/V the parity of (k AND 7) XOR B.
3144
+ */
3145
+ _blockIOFlags(value, k) {
3146
+ const b = this.registers.B;
3147
+ this.flagS = (b & 0x80) !== 0;
3148
+ this.flagZ = b === 0;
3149
+ this._xy(b);
3150
+ this.flagN = (value & 0x80) !== 0;
3151
+ this.flagH = k > 0xff;
3152
+ this.flagC = k > 0xff;
3153
+ this.flagPV = this.parity((k & 7) ^ b);
3154
+ }
3155
+
3156
+ /**
3157
+ * INI - Input and Increment
3158
+ * Inputs from port C to (HL), increments HL, decrements B
3159
+ */
3160
+ ini() {
3161
+ this.wz = (this.BC + 1) & 0xffff;
3162
+ const port = this.registers.C;
3163
+ const value = this.readPort(port);
3164
+ this.writeMemory(this.HL, value);
3165
+ this.HL = (this.HL + 1) & 0xffff;
3166
+ this.registers.B = (this.registers.B - 1) & 0xff;
3167
+
3168
+ // Flag updates
3169
+ this._blockIOFlags(value, value + ((port + 1) & 0xff));
3170
+
3171
+ return 16;
3172
+ }
3173
+
3174
+ /**
3175
+ * INIR - Input, Increment, Repeat
3176
+ * Repeats INI until B = 0
3177
+ */
3178
+ inir() {
3179
+ const cycles = this.ini();
3180
+ if (this.registers.B !== 0) {
3181
+ this.registers.PC = (this.registers.PC - 2) & 0xffff;
3182
+ return cycles + 5;
3183
+ }
3184
+ return cycles;
3185
+ }
3186
+
3187
+ /**
3188
+ * IND - Input and Decrement
3189
+ */
3190
+ ind() {
3191
+ this.wz = (this.BC - 1) & 0xffff;
3192
+ const port = this.registers.C;
3193
+ const value = this.readPort(port);
3194
+ this.writeMemory(this.HL, value);
3195
+ this.HL = (this.HL - 1) & 0xffff;
3196
+ this.registers.B = (this.registers.B - 1) & 0xff;
3197
+
3198
+ this._blockIOFlags(value, value + ((port - 1) & 0xff));
3199
+
3200
+ return 16;
3201
+ }
3202
+
3203
+ /**
3204
+ * INDR - Input, Decrement, Repeat
3205
+ */
3206
+ indr() {
3207
+ const cycles = this.ind();
3208
+ if (this.registers.B !== 0) {
3209
+ this.registers.PC = (this.registers.PC - 2) & 0xffff;
3210
+ return cycles + 5;
3211
+ }
3212
+ return cycles;
3213
+ }
3214
+
3215
+ /**
3216
+ * OUTI - Output and Increment
3217
+ */
3218
+ outi() {
3219
+ const port = this.registers.C;
3220
+ const value = this.readMemory(this.HL);
3221
+ this.writePort(port, value);
3222
+ this.HL = (this.HL + 1) & 0xffff;
3223
+ this.registers.B = (this.registers.B - 1) & 0xff;
3224
+ this.wz = (this.BC + 1) & 0xffff;
3225
+
3226
+ this._blockIOFlags(value, value + this.registers.L);
3227
+
3228
+ return 16;
3229
+ }
3230
+
3231
+ /**
3232
+ * OTIR - Output, Increment, Repeat
3233
+ */
3234
+ otir() {
3235
+ const cycles = this.outi();
3236
+ if (this.registers.B !== 0) {
3237
+ this.registers.PC = (this.registers.PC - 2) & 0xffff;
3238
+ return cycles + 5;
3239
+ }
3240
+ return cycles;
3241
+ }
3242
+
3243
+ /**
3244
+ * OUTD - Output and Decrement
3245
+ */
3246
+ outd() {
3247
+ const port = this.registers.C;
3248
+ const value = this.readMemory(this.HL);
3249
+ this.writePort(port, value);
3250
+ this.HL = (this.HL - 1) & 0xffff;
3251
+ this.registers.B = (this.registers.B - 1) & 0xff;
3252
+ this.wz = (this.BC - 1) & 0xffff;
3253
+
3254
+ this._blockIOFlags(value, value + this.registers.L);
3255
+
3256
+ return 16;
3257
+ }
3258
+
3259
+ /**
3260
+ * OTDR - Output, Decrement, Repeat
3261
+ */
3262
+ otdr() {
3263
+ const cycles = this.outd();
3264
+ if (this.registers.B !== 0) {
3265
+ this.registers.PC = (this.registers.PC - 2) & 0xffff;
3266
+ return cycles + 5;
3267
+ }
3268
+ return cycles;
3269
+ }
3270
+
3271
+ // Interrupt Handling Instructions
3272
+ /**
3273
+ * RETI - Return from Interrupt
3274
+ * Same as RET but signals interrupt controller
3275
+ */
3276
+ reti() {
3277
+ const low = this.readMemory(this.registers.SP);
3278
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
3279
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
3280
+ this.registers.PC = (high << 8) | low;
3281
+ this.wz = this.registers.PC;
3282
+ // IFF2 is copied to IFF1 on RETI
3283
+ this.IFF1 = this.IFF2;
3284
+ return 14;
3285
+ }
3286
+
3287
+ /**
3288
+ * RETN - Return from Non-Maskable Interrupt
3289
+ * Same as RET but restores IFF1 from IFF2
3290
+ */
3291
+ retn() {
3292
+ const low = this.readMemory(this.registers.SP);
3293
+ const high = this.readMemory((this.registers.SP + 1) & 0xffff);
3294
+ this.registers.SP = (this.registers.SP + 2) & 0xffff;
3295
+ this.registers.PC = (high << 8) | low;
3296
+ this.wz = this.registers.PC;
3297
+ // IFF2 is copied to IFF1 on RETN
3298
+ this.IFF1 = this.IFF2;
3299
+ return 14;
3300
+ }
3301
+
3302
+ /**
3303
+ * RLD - Rotate the three low nibbles left: A.low -> (HL).low,
3304
+ * (HL).low -> (HL).high, (HL).high -> A.low. C is unaffected.
3305
+ */
3306
+ rld() {
3307
+ const value = this.readMemory(this.HL);
3308
+ this.writeMemory(
3309
+ this.HL,
3310
+ ((value << 4) & 0xf0) | (this.registers.A & 0x0f)
3311
+ );
3312
+ this.registers.A = (this.registers.A & 0xf0) | (value >> 4);
3313
+ this.wz = (this.HL + 1) & 0xffff;
3314
+ this._nibbleRotateFlags();
3315
+ return 18;
3316
+ }
3317
+
3318
+ /**
3319
+ * RRD - Rotate the three low nibbles right: A.low -> (HL).high,
3320
+ * (HL).high -> (HL).low, (HL).low -> A.low. C is unaffected.
3321
+ */
3322
+ rrd() {
3323
+ const value = this.readMemory(this.HL);
3324
+ this.writeMemory(
3325
+ this.HL,
3326
+ ((this.registers.A & 0x0f) << 4) | (value >> 4)
3327
+ );
3328
+ this.registers.A = (this.registers.A & 0xf0) | (value & 0x0f);
3329
+ this.wz = (this.HL + 1) & 0xffff;
3330
+ this._nibbleRotateFlags();
3331
+ return 18;
3332
+ }
3333
+
3334
+ _nibbleRotateFlags() {
3335
+ const a = this.registers.A;
3336
+ this.flagS = (a & 0x80) !== 0;
3337
+ this.flagZ = a === 0;
3338
+ this.flagH = false;
3339
+ this.flagPV = this.parity(a);
3340
+ this.flagN = false;
3341
+ this._xy(a);
3342
+ }
3343
+ }