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