@joezilla/8sim 0.10.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.
Files changed (320) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +542 -0
  3. package/dist/8sim.browser.js +4728 -0
  4. package/dist/bundles/CardBundle.d.ts +83 -0
  5. package/dist/bundles/CardBundle.d.ts.map +1 -0
  6. package/dist/bundles/CardBundle.js +41 -0
  7. package/dist/bundles/CardBundle.js.map +1 -0
  8. package/dist/bundles/kernels.d.ts +48 -0
  9. package/dist/bundles/kernels.d.ts.map +1 -0
  10. package/dist/bundles/kernels.js +132 -0
  11. package/dist/bundles/kernels.js.map +1 -0
  12. package/dist/bundles/seed/index.d.ts +24 -0
  13. package/dist/bundles/seed/index.d.ts.map +1 -0
  14. package/dist/bundles/seed/index.js +266 -0
  15. package/dist/bundles/seed/index.js.map +1 -0
  16. package/dist/bus/Bus.d.ts +21 -0
  17. package/dist/bus/Bus.d.ts.map +1 -0
  18. package/dist/bus/Bus.js +62 -0
  19. package/dist/bus/Bus.js.map +1 -0
  20. package/dist/bus/BusRegion.d.ts +8 -0
  21. package/dist/bus/BusRegion.d.ts.map +1 -0
  22. package/dist/bus/BusRegion.js +8 -0
  23. package/dist/bus/BusRegion.js.map +1 -0
  24. package/dist/bus/SnoopBus.d.ts +15 -0
  25. package/dist/bus/SnoopBus.d.ts.map +1 -0
  26. package/dist/bus/SnoopBus.js +41 -0
  27. package/dist/bus/SnoopBus.js.map +1 -0
  28. package/dist/cards/BankRamCard.d.ts +35 -0
  29. package/dist/cards/BankRamCard.d.ts.map +1 -0
  30. package/dist/cards/BankRamCard.js +56 -0
  31. package/dist/cards/BankRamCard.js.map +1 -0
  32. package/dist/cards/DazzlerCard.d.ts +42 -0
  33. package/dist/cards/DazzlerCard.d.ts.map +1 -0
  34. package/dist/cards/DazzlerCard.js +83 -0
  35. package/dist/cards/DazzlerCard.js.map +1 -0
  36. package/dist/cards/DisplaySurface.d.ts +32 -0
  37. package/dist/cards/DisplaySurface.d.ts.map +1 -0
  38. package/dist/cards/DisplaySurface.js +11 -0
  39. package/dist/cards/DisplaySurface.js.map +1 -0
  40. package/dist/cards/FdcPlusClient.d.ts +35 -0
  41. package/dist/cards/FdcPlusClient.d.ts.map +1 -0
  42. package/dist/cards/FdcPlusClient.js +130 -0
  43. package/dist/cards/FdcPlusClient.js.map +1 -0
  44. package/dist/cards/ImsaiMioCard.d.ts +36 -0
  45. package/dist/cards/ImsaiMioCard.d.ts.map +1 -0
  46. package/dist/cards/ImsaiMioCard.js +48 -0
  47. package/dist/cards/ImsaiMioCard.js.map +1 -0
  48. package/dist/cards/ImsaiSioCard.d.ts +19 -0
  49. package/dist/cards/ImsaiSioCard.d.ts.map +1 -0
  50. package/dist/cards/ImsaiSioCard.js +54 -0
  51. package/dist/cards/ImsaiSioCard.js.map +1 -0
  52. package/dist/cards/KeyboardCard.d.ts +37 -0
  53. package/dist/cards/KeyboardCard.d.ts.map +1 -0
  54. package/dist/cards/KeyboardCard.js +79 -0
  55. package/dist/cards/KeyboardCard.js.map +1 -0
  56. package/dist/cards/Mc6850Acia.d.ts +68 -0
  57. package/dist/cards/Mc6850Acia.d.ts.map +1 -0
  58. package/dist/cards/Mc6850Acia.js +132 -0
  59. package/dist/cards/Mc6850Acia.js.map +1 -0
  60. package/dist/cards/Mits2SioCard.d.ts +27 -0
  61. package/dist/cards/Mits2SioCard.d.ts.map +1 -0
  62. package/dist/cards/Mits2SioCard.js +36 -0
  63. package/dist/cards/Mits2SioCard.js.map +1 -0
  64. package/dist/cards/MitsDcddCard.d.ts +52 -0
  65. package/dist/cards/MitsDcddCard.d.ts.map +1 -0
  66. package/dist/cards/MitsDcddCard.js +294 -0
  67. package/dist/cards/MitsDcddCard.js.map +1 -0
  68. package/dist/cards/ParallelCard.d.ts +35 -0
  69. package/dist/cards/ParallelCard.d.ts.map +1 -0
  70. package/dist/cards/ParallelCard.js +32 -0
  71. package/dist/cards/ParallelCard.js.map +1 -0
  72. package/dist/cards/Port8212.d.ts +31 -0
  73. package/dist/cards/Port8212.d.ts.map +1 -0
  74. package/dist/cards/Port8212.js +47 -0
  75. package/dist/cards/Port8212.js.map +1 -0
  76. package/dist/cards/RtcCard.d.ts +30 -0
  77. package/dist/cards/RtcCard.d.ts.map +1 -0
  78. package/dist/cards/RtcCard.js +61 -0
  79. package/dist/cards/RtcCard.js.map +1 -0
  80. package/dist/cards/SerialCard.d.ts +31 -0
  81. package/dist/cards/SerialCard.d.ts.map +1 -0
  82. package/dist/cards/SerialCard.js +28 -0
  83. package/dist/cards/SerialCard.js.map +1 -0
  84. package/dist/cards/Tr1602Uart.d.ts +55 -0
  85. package/dist/cards/Tr1602Uart.d.ts.map +1 -0
  86. package/dist/cards/Tr1602Uart.js +102 -0
  87. package/dist/cards/Tr1602Uart.js.map +1 -0
  88. package/dist/cards/Usart8251.d.ts +28 -0
  89. package/dist/cards/Usart8251.d.ts.map +1 -0
  90. package/dist/cards/Usart8251.js +88 -0
  91. package/dist/cards/Usart8251.js.map +1 -0
  92. package/dist/cards/VdmCard.d.ts +27 -0
  93. package/dist/cards/VdmCard.d.ts.map +1 -0
  94. package/dist/cards/VdmCard.js +40 -0
  95. package/dist/cards/VdmCard.js.map +1 -0
  96. package/dist/clock/ImmediateClock.d.ts +8 -0
  97. package/dist/clock/ImmediateClock.d.ts.map +1 -0
  98. package/dist/clock/ImmediateClock.js +13 -0
  99. package/dist/clock/ImmediateClock.js.map +1 -0
  100. package/dist/clock/SystemClock.d.ts +45 -0
  101. package/dist/clock/SystemClock.d.ts.map +1 -0
  102. package/dist/clock/SystemClock.js +71 -0
  103. package/dist/clock/SystemClock.js.map +1 -0
  104. package/dist/cpu/Cpu8080.d.ts +34 -0
  105. package/dist/cpu/Cpu8080.d.ts.map +1 -0
  106. package/dist/cpu/Cpu8080.js +126 -0
  107. package/dist/cpu/Cpu8080.js.map +1 -0
  108. package/dist/cpu/Decoder.d.ts +12 -0
  109. package/dist/cpu/Decoder.d.ts.map +1 -0
  110. package/dist/cpu/Decoder.js +23 -0
  111. package/dist/cpu/Decoder.js.map +1 -0
  112. package/dist/cpu/Flags.d.ts +18 -0
  113. package/dist/cpu/Flags.d.ts.map +1 -0
  114. package/dist/cpu/Flags.js +33 -0
  115. package/dist/cpu/Flags.js.map +1 -0
  116. package/dist/cpu/Registers.d.ts +22 -0
  117. package/dist/cpu/Registers.d.ts.map +1 -0
  118. package/dist/cpu/Registers.js +26 -0
  119. package/dist/cpu/Registers.js.map +1 -0
  120. package/dist/cpu/instructions/alu.d.ts +3 -0
  121. package/dist/cpu/instructions/alu.d.ts.map +1 -0
  122. package/dist/cpu/instructions/alu.js +221 -0
  123. package/dist/cpu/instructions/alu.js.map +1 -0
  124. package/dist/cpu/instructions/branch.d.ts +3 -0
  125. package/dist/cpu/instructions/branch.d.ts.map +1 -0
  126. package/dist/cpu/instructions/branch.js +117 -0
  127. package/dist/cpu/instructions/branch.js.map +1 -0
  128. package/dist/cpu/instructions/control.d.ts +3 -0
  129. package/dist/cpu/instructions/control.d.ts.map +1 -0
  130. package/dist/cpu/instructions/control.js +12 -0
  131. package/dist/cpu/instructions/control.js.map +1 -0
  132. package/dist/cpu/instructions/data.d.ts +3 -0
  133. package/dist/cpu/instructions/data.d.ts.map +1 -0
  134. package/dist/cpu/instructions/data.js +137 -0
  135. package/dist/cpu/instructions/data.js.map +1 -0
  136. package/dist/cpu/instructions/io.d.ts +3 -0
  137. package/dist/cpu/instructions/io.d.ts.map +1 -0
  138. package/dist/cpu/instructions/io.js +18 -0
  139. package/dist/cpu/instructions/io.js.map +1 -0
  140. package/dist/cpu/instructions/logical.d.ts +3 -0
  141. package/dist/cpu/instructions/logical.d.ts.map +1 -0
  142. package/dist/cpu/instructions/logical.js +129 -0
  143. package/dist/cpu/instructions/logical.js.map +1 -0
  144. package/dist/cpu/instructions/rotate.d.ts +3 -0
  145. package/dist/cpu/instructions/rotate.d.ts.map +1 -0
  146. package/dist/cpu/instructions/rotate.js +34 -0
  147. package/dist/cpu/instructions/rotate.js.map +1 -0
  148. package/dist/cpu/instructions/stack.d.ts +3 -0
  149. package/dist/cpu/instructions/stack.d.ts.map +1 -0
  150. package/dist/cpu/instructions/stack.js +84 -0
  151. package/dist/cpu/instructions/stack.js.map +1 -0
  152. package/dist/cpu/status8080.d.ts +33 -0
  153. package/dist/cpu/status8080.d.ts.map +1 -0
  154. package/dist/cpu/status8080.js +73 -0
  155. package/dist/cpu/status8080.js.map +1 -0
  156. package/dist/cpu/z80/CpuZ80.d.ts +53 -0
  157. package/dist/cpu/z80/CpuZ80.d.ts.map +1 -0
  158. package/dist/cpu/z80/CpuZ80.js +168 -0
  159. package/dist/cpu/z80/CpuZ80.js.map +1 -0
  160. package/dist/cpu/z80/DecoderZ80.d.ts +26 -0
  161. package/dist/cpu/z80/DecoderZ80.d.ts.map +1 -0
  162. package/dist/cpu/z80/DecoderZ80.js +107 -0
  163. package/dist/cpu/z80/DecoderZ80.js.map +1 -0
  164. package/dist/cpu/z80/FlagsZ80.d.ts +27 -0
  165. package/dist/cpu/z80/FlagsZ80.d.ts.map +1 -0
  166. package/dist/cpu/z80/FlagsZ80.js +47 -0
  167. package/dist/cpu/z80/FlagsZ80.js.map +1 -0
  168. package/dist/cpu/z80/RegistersZ80.d.ts +60 -0
  169. package/dist/cpu/z80/RegistersZ80.d.ts.map +1 -0
  170. package/dist/cpu/z80/RegistersZ80.js +90 -0
  171. package/dist/cpu/z80/RegistersZ80.js.map +1 -0
  172. package/dist/cpu/z80/flagHelpers.d.ts +25 -0
  173. package/dist/cpu/z80/flagHelpers.d.ts.map +1 -0
  174. package/dist/cpu/z80/flagHelpers.js +136 -0
  175. package/dist/cpu/z80/flagHelpers.js.map +1 -0
  176. package/dist/cpu/z80/instructions/alu16.d.ts +4 -0
  177. package/dist/cpu/z80/instructions/alu16.d.ts.map +1 -0
  178. package/dist/cpu/z80/instructions/alu16.js +27 -0
  179. package/dist/cpu/z80/instructions/alu16.js.map +1 -0
  180. package/dist/cpu/z80/instructions/alu8.d.ts +4 -0
  181. package/dist/cpu/z80/instructions/alu8.d.ts.map +1 -0
  182. package/dist/cpu/z80/instructions/alu8.js +100 -0
  183. package/dist/cpu/z80/instructions/alu8.js.map +1 -0
  184. package/dist/cpu/z80/instructions/bits.d.ts +10 -0
  185. package/dist/cpu/z80/instructions/bits.d.ts.map +1 -0
  186. package/dist/cpu/z80/instructions/bits.js +164 -0
  187. package/dist/cpu/z80/instructions/bits.js.map +1 -0
  188. package/dist/cpu/z80/instructions/block.d.ts +10 -0
  189. package/dist/cpu/z80/instructions/block.d.ts.map +1 -0
  190. package/dist/cpu/z80/instructions/block.js +141 -0
  191. package/dist/cpu/z80/instructions/block.js.map +1 -0
  192. package/dist/cpu/z80/instructions/control.d.ts +4 -0
  193. package/dist/cpu/z80/instructions/control.d.ts.map +1 -0
  194. package/dist/cpu/z80/instructions/control.js +62 -0
  195. package/dist/cpu/z80/instructions/control.js.map +1 -0
  196. package/dist/cpu/z80/instructions/ed.d.ts +4 -0
  197. package/dist/cpu/z80/instructions/ed.d.ts.map +1 -0
  198. package/dist/cpu/z80/instructions/ed.js +149 -0
  199. package/dist/cpu/z80/instructions/ed.js.map +1 -0
  200. package/dist/cpu/z80/instructions/exchange.d.ts +4 -0
  201. package/dist/cpu/z80/instructions/exchange.d.ts.map +1 -0
  202. package/dist/cpu/z80/instructions/exchange.js +37 -0
  203. package/dist/cpu/z80/instructions/exchange.js.map +1 -0
  204. package/dist/cpu/z80/instructions/io.d.ts +8 -0
  205. package/dist/cpu/z80/instructions/io.d.ts.map +1 -0
  206. package/dist/cpu/z80/instructions/io.js +22 -0
  207. package/dist/cpu/z80/instructions/io.js.map +1 -0
  208. package/dist/cpu/z80/instructions/jump.d.ts +4 -0
  209. package/dist/cpu/z80/instructions/jump.d.ts.map +1 -0
  210. package/dist/cpu/z80/instructions/jump.js +113 -0
  211. package/dist/cpu/z80/instructions/jump.js.map +1 -0
  212. package/dist/cpu/z80/instructions/load.d.ts +7 -0
  213. package/dist/cpu/z80/instructions/load.d.ts.map +1 -0
  214. package/dist/cpu/z80/instructions/load.js +103 -0
  215. package/dist/cpu/z80/instructions/load.js.map +1 -0
  216. package/dist/cpu/z80/instructions/rotate.d.ts +9 -0
  217. package/dist/cpu/z80/instructions/rotate.d.ts.map +1 -0
  218. package/dist/cpu/z80/instructions/rotate.js +48 -0
  219. package/dist/cpu/z80/instructions/rotate.js.map +1 -0
  220. package/dist/cpu/z80/instructions/stack.d.ts +4 -0
  221. package/dist/cpu/z80/instructions/stack.d.ts.map +1 -0
  222. package/dist/cpu/z80/instructions/stack.js +19 -0
  223. package/dist/cpu/z80/instructions/stack.js.map +1 -0
  224. package/dist/cpu/z80/regcodes.d.ts +22 -0
  225. package/dist/cpu/z80/regcodes.d.ts.map +1 -0
  226. package/dist/cpu/z80/regcodes.js +93 -0
  227. package/dist/cpu/z80/regcodes.js.map +1 -0
  228. package/dist/cpu/z80/types.d.ts +59 -0
  229. package/dist/cpu/z80/types.d.ts.map +1 -0
  230. package/dist/cpu/z80/types.js +2 -0
  231. package/dist/cpu/z80/types.js.map +1 -0
  232. package/dist/cpu/z80/views.d.ts +8 -0
  233. package/dist/cpu/z80/views.d.ts.map +1 -0
  234. package/dist/cpu/z80/views.js +40 -0
  235. package/dist/cpu/z80/views.js.map +1 -0
  236. package/dist/index.d.ts +67 -0
  237. package/dist/index.d.ts.map +1 -0
  238. package/dist/index.js +49 -0
  239. package/dist/index.js.map +1 -0
  240. package/dist/interfaces/IBus.d.ts +8 -0
  241. package/dist/interfaces/IBus.d.ts.map +1 -0
  242. package/dist/interfaces/IBus.js +2 -0
  243. package/dist/interfaces/IBus.js.map +1 -0
  244. package/dist/interfaces/IBusObserver.d.ts +7 -0
  245. package/dist/interfaces/IBusObserver.d.ts.map +1 -0
  246. package/dist/interfaces/IBusObserver.js +2 -0
  247. package/dist/interfaces/IBusObserver.js.map +1 -0
  248. package/dist/interfaces/IClock.d.ts +6 -0
  249. package/dist/interfaces/IClock.d.ts.map +1 -0
  250. package/dist/interfaces/IClock.js +2 -0
  251. package/dist/interfaces/IClock.js.map +1 -0
  252. package/dist/interfaces/ICpu.d.ts +46 -0
  253. package/dist/interfaces/ICpu.d.ts.map +1 -0
  254. package/dist/interfaces/ICpu.js +2 -0
  255. package/dist/interfaces/ICpu.js.map +1 -0
  256. package/dist/interfaces/IIODevice.d.ts +7 -0
  257. package/dist/interfaces/IIODevice.d.ts.map +1 -0
  258. package/dist/interfaces/IIODevice.js +2 -0
  259. package/dist/interfaces/IIODevice.js.map +1 -0
  260. package/dist/interfaces/IInterruptController.d.ts +8 -0
  261. package/dist/interfaces/IInterruptController.d.ts.map +1 -0
  262. package/dist/interfaces/IInterruptController.js +2 -0
  263. package/dist/interfaces/IInterruptController.js.map +1 -0
  264. package/dist/interfaces/IMemory.d.ts +9 -0
  265. package/dist/interfaces/IMemory.d.ts.map +1 -0
  266. package/dist/interfaces/IMemory.js +2 -0
  267. package/dist/interfaces/IMemory.js.map +1 -0
  268. package/dist/interfaces/IModule.d.ts +5 -0
  269. package/dist/interfaces/IModule.d.ts.map +1 -0
  270. package/dist/interfaces/IModule.js +2 -0
  271. package/dist/interfaces/IModule.js.map +1 -0
  272. package/dist/interfaces/IS100Card.d.ts +6 -0
  273. package/dist/interfaces/IS100Card.d.ts.map +1 -0
  274. package/dist/interfaces/IS100Card.js +2 -0
  275. package/dist/interfaces/IS100Card.js.map +1 -0
  276. package/dist/interfaces/index.d.ts +10 -0
  277. package/dist/interfaces/index.d.ts.map +1 -0
  278. package/dist/interfaces/index.js +2 -0
  279. package/dist/interfaces/index.js.map +1 -0
  280. package/dist/interrupt/InterruptController.d.ts +13 -0
  281. package/dist/interrupt/InterruptController.d.ts.map +1 -0
  282. package/dist/interrupt/InterruptController.js +36 -0
  283. package/dist/interrupt/InterruptController.js.map +1 -0
  284. package/dist/io/IoSpace.d.ts +9 -0
  285. package/dist/io/IoSpace.d.ts.map +1 -0
  286. package/dist/io/IoSpace.js +30 -0
  287. package/dist/io/IoSpace.js.map +1 -0
  288. package/dist/machine/MachineRunner.d.ts +54 -0
  289. package/dist/machine/MachineRunner.d.ts.map +1 -0
  290. package/dist/machine/MachineRunner.js +102 -0
  291. package/dist/machine/MachineRunner.js.map +1 -0
  292. package/dist/machine/MachineSpec.d.ts +80 -0
  293. package/dist/machine/MachineSpec.d.ts.map +1 -0
  294. package/dist/machine/MachineSpec.js +9 -0
  295. package/dist/machine/MachineSpec.js.map +1 -0
  296. package/dist/machine/buildMachine.d.ts +19 -0
  297. package/dist/machine/buildMachine.d.ts.map +1 -0
  298. package/dist/machine/buildMachine.js +122 -0
  299. package/dist/machine/buildMachine.js.map +1 -0
  300. package/dist/memory/MemoryMappedIOAdapter.d.ts +14 -0
  301. package/dist/memory/MemoryMappedIOAdapter.d.ts.map +1 -0
  302. package/dist/memory/MemoryMappedIOAdapter.js +23 -0
  303. package/dist/memory/MemoryMappedIOAdapter.js.map +1 -0
  304. package/dist/memory/Ram.d.ts +17 -0
  305. package/dist/memory/Ram.d.ts.map +1 -0
  306. package/dist/memory/Ram.js +36 -0
  307. package/dist/memory/Ram.js.map +1 -0
  308. package/dist/memory/Rom.d.ts +13 -0
  309. package/dist/memory/Rom.d.ts.map +1 -0
  310. package/dist/memory/Rom.js +25 -0
  311. package/dist/memory/Rom.js.map +1 -0
  312. package/dist/util/bits.d.ts +11 -0
  313. package/dist/util/bits.d.ts.map +1 -0
  314. package/dist/util/bits.js +35 -0
  315. package/dist/util/bits.js.map +1 -0
  316. package/dist/util/hostConsole.d.ts +2 -0
  317. package/dist/util/hostConsole.d.ts.map +1 -0
  318. package/dist/util/hostConsole.js +4 -0
  319. package/dist/util/hostConsole.js.map +1 -0
  320. package/package.json +39 -0
@@ -0,0 +1,4728 @@
1
+ // src/cpu/Registers.ts
2
+ var Registers = class {
3
+ a = 0;
4
+ b = 0;
5
+ c = 0;
6
+ d = 0;
7
+ e = 0;
8
+ h = 0;
9
+ l = 0;
10
+ sp = 0;
11
+ pc = 0;
12
+ /** BC register pair */
13
+ get bc() {
14
+ return this.b << 8 | this.c;
15
+ }
16
+ set bc(v) {
17
+ this.b = v >> 8 & 255;
18
+ this.c = v & 255;
19
+ }
20
+ /** DE register pair */
21
+ get de() {
22
+ return this.d << 8 | this.e;
23
+ }
24
+ set de(v) {
25
+ this.d = v >> 8 & 255;
26
+ this.e = v & 255;
27
+ }
28
+ /** HL register pair */
29
+ get hl() {
30
+ return this.h << 8 | this.l;
31
+ }
32
+ set hl(v) {
33
+ this.h = v >> 8 & 255;
34
+ this.l = v & 255;
35
+ }
36
+ reset() {
37
+ this.a = this.b = this.c = this.d = this.e = this.h = this.l = 0;
38
+ this.sp = 0;
39
+ this.pc = 0;
40
+ }
41
+ };
42
+
43
+ // src/cpu/Flags.ts
44
+ var Flags = class {
45
+ s = false;
46
+ // Sign
47
+ z = false;
48
+ // Zero
49
+ ac = false;
50
+ // Auxiliary Carry (half-carry)
51
+ p = false;
52
+ // Parity (even)
53
+ cy = false;
54
+ // Carry
55
+ /** Serialize to PSW byte */
56
+ toByte() {
57
+ return (this.s ? 128 : 0) | (this.z ? 64 : 0) | (this.ac ? 16 : 0) | (this.p ? 4 : 0) | 2 | // bit 1 always 1
58
+ (this.cy ? 1 : 0);
59
+ }
60
+ /** Deserialize from PSW byte */
61
+ fromByte(b) {
62
+ this.s = (b & 128) !== 0;
63
+ this.z = (b & 64) !== 0;
64
+ this.ac = (b & 16) !== 0;
65
+ this.p = (b & 4) !== 0;
66
+ this.cy = (b & 1) !== 0;
67
+ }
68
+ reset() {
69
+ this.s = this.z = this.ac = this.p = this.cy = false;
70
+ }
71
+ };
72
+
73
+ // src/cpu/Decoder.ts
74
+ var Decoder = class {
75
+ table;
76
+ constructor() {
77
+ this.table = new Array(256).fill(void 0).map((_, i) => {
78
+ return (_regs, _flags, _bus) => {
79
+ console.warn(`Unimplemented opcode: 0x${i.toString(16).padStart(2, "0")}`);
80
+ return 4;
81
+ };
82
+ });
83
+ }
84
+ register(opcode, handler) {
85
+ this.table[opcode] = handler;
86
+ }
87
+ registerMany(opcodes, handler) {
88
+ for (const op of opcodes) {
89
+ this.register(op, handler);
90
+ }
91
+ }
92
+ decode(opcode) {
93
+ return this.table[opcode & 255];
94
+ }
95
+ };
96
+
97
+ // src/cpu/status8080.ts
98
+ var STATUS = {
99
+ INTA: 1,
100
+ // interrupt acknowledge
101
+ WO: 2,
102
+ // write/output — active LOW (set = a read cycle)
103
+ STACK: 4,
104
+ // stack access
105
+ HLTA: 8,
106
+ // halt acknowledge
107
+ OUT: 16,
108
+ // output write
109
+ M1: 32,
110
+ // first machine cycle (opcode fetch)
111
+ INP: 64,
112
+ // input read
113
+ MEMR: 128
114
+ // memory read
115
+ };
116
+ var isRst = (op) => (op & 199) === 199;
117
+ var writesMemory = (op) => op === 50 || // STA
118
+ op === 34 || // SHLD
119
+ op === 2 || op === 18 || // STAX B/D
120
+ op === 54 || // MVI M,d8
121
+ op >= 112 && op <= 119 && op !== 118 || // MOV M,r
122
+ op === 197 || op === 213 || op === 229 || op === 245 || // PUSH
123
+ op === 205 || op === 196 || op === 204 || op === 212 || op === 220 || op === 228 || op === 236 || op === 244 || op === 252 || // CALL (all)
124
+ op === 227 || // XTHL
125
+ isRst(op);
126
+ var touchesStack = (op) => op === 197 || op === 213 || op === 229 || op === 245 || // PUSH
127
+ op === 193 || op === 209 || op === 225 || op === 241 || // POP
128
+ op === 205 || op === 196 || op === 204 || op === 212 || op === 220 || op === 228 || op === 236 || op === 244 || op === 252 || // CALL (all)
129
+ op === 201 || op === 192 || op === 200 || op === 208 || op === 216 || op === 224 || op === 232 || op === 240 || op === 248 || // RET (all)
130
+ op === 227 || // XTHL
131
+ isRst(op);
132
+ function statusByteForOpcode(op) {
133
+ let s = STATUS.M1 | STATUS.MEMR | STATUS.WO;
134
+ if (op === 118) return s | STATUS.HLTA;
135
+ if (op === 219) s |= STATUS.INP;
136
+ if (op === 211) s = (s | STATUS.OUT) & ~STATUS.WO;
137
+ if (touchesStack(op)) s |= STATUS.STACK;
138
+ if (writesMemory(op)) s &= ~STATUS.WO;
139
+ return s;
140
+ }
141
+ function buildStatusTable() {
142
+ const t = new Uint8Array(256);
143
+ for (let op = 0; op < 256; op++) t[op] = statusByteForOpcode(op);
144
+ return t;
145
+ }
146
+ var INTA_STATUS = STATUS.INTA | STATUS.M1 | STATUS.WO;
147
+ var FETCH_STATUS = STATUS.M1 | STATUS.MEMR | STATUS.WO;
148
+
149
+ // src/cpu/instructions/control.ts
150
+ function registerControl(decoder) {
151
+ decoder.register(0, (_regs, _flags, _bus) => 4);
152
+ decoder.register(118, (_regs, _flags, _bus) => 7);
153
+ decoder.register(251, (_regs, _flags, _bus) => 4);
154
+ decoder.register(243, (_regs, _flags, _bus) => 4);
155
+ }
156
+
157
+ // src/util/bits.ts
158
+ function u8(v) {
159
+ return v & 255;
160
+ }
161
+ function u16(v) {
162
+ return v & 65535;
163
+ }
164
+ function signBit(v) {
165
+ return (v & 128) !== 0;
166
+ }
167
+ function zeroFlag(v) {
168
+ return (v & 255) === 0;
169
+ }
170
+ function parityFlag(v) {
171
+ let x = v & 255;
172
+ x ^= x >> 4;
173
+ x ^= x >> 2;
174
+ x ^= x >> 1;
175
+ return (x & 1) === 0;
176
+ }
177
+ function auxCarryAdd(a, b, carry = 0) {
178
+ return (a & 15) + (b & 15) + carry > 15;
179
+ }
180
+ function auxCarrySub(a, b, borrow = 0) {
181
+ return (a & 15) - (b & 15) - borrow < 0;
182
+ }
183
+ function toWord(hi2, lo2) {
184
+ return (hi2 & 255) << 8 | lo2 & 255;
185
+ }
186
+ function hi(w) {
187
+ return w >> 8 & 255;
188
+ }
189
+ function lo(w) {
190
+ return w & 255;
191
+ }
192
+
193
+ // src/cpu/instructions/data.ts
194
+ var REG_ORDER = ["b", "c", "d", "e", "h", "l", "M", "a"];
195
+ function getReg(regs, r, bus) {
196
+ if (r === "M") return bus.read(regs.hl);
197
+ return regs[r];
198
+ }
199
+ function setReg(regs, r, val, bus) {
200
+ if (r === "M") {
201
+ bus.write(regs.hl, u8(val));
202
+ return;
203
+ }
204
+ regs[r] = u8(val);
205
+ }
206
+ function registerData(decoder) {
207
+ for (let dst = 0; dst < 8; dst++) {
208
+ for (let src = 0; src < 8; src++) {
209
+ const opcode = 64 | dst << 3 | src;
210
+ if (opcode === 118) continue;
211
+ const dstReg = REG_ORDER[dst];
212
+ const srcReg = REG_ORDER[src];
213
+ const cycles = dstReg === "M" || srcReg === "M" ? 7 : 5;
214
+ decoder.register(opcode, (regs, _flags, bus) => {
215
+ const val = getReg(regs, srcReg, bus);
216
+ setReg(regs, dstReg, val, bus);
217
+ return cycles;
218
+ });
219
+ }
220
+ }
221
+ for (let r = 0; r < 8; r++) {
222
+ const opcode = 6 | r << 3;
223
+ const reg = REG_ORDER[r];
224
+ const cycles = reg === "M" ? 10 : 7;
225
+ decoder.register(opcode, (regs, _flags, bus) => {
226
+ const imm = bus.read(regs.pc);
227
+ regs.pc = u16(regs.pc + 1);
228
+ setReg(regs, reg, imm, bus);
229
+ return cycles;
230
+ });
231
+ }
232
+ decoder.register(1, (regs, _flags, bus) => {
233
+ regs.c = bus.read(regs.pc);
234
+ regs.b = bus.read(u16(regs.pc + 1));
235
+ regs.pc = u16(regs.pc + 2);
236
+ return 10;
237
+ });
238
+ decoder.register(17, (regs, _flags, bus) => {
239
+ regs.e = bus.read(regs.pc);
240
+ regs.d = bus.read(u16(regs.pc + 1));
241
+ regs.pc = u16(regs.pc + 2);
242
+ return 10;
243
+ });
244
+ decoder.register(33, (regs, _flags, bus) => {
245
+ regs.l = bus.read(regs.pc);
246
+ regs.h = bus.read(u16(regs.pc + 1));
247
+ regs.pc = u16(regs.pc + 2);
248
+ return 10;
249
+ });
250
+ decoder.register(49, (regs, _flags, bus) => {
251
+ const lo_ = bus.read(regs.pc);
252
+ const hi_ = bus.read(u16(regs.pc + 1));
253
+ regs.sp = u16(toWord(hi_, lo_));
254
+ regs.pc = u16(regs.pc + 2);
255
+ return 10;
256
+ });
257
+ decoder.register(58, (regs, _flags, bus) => {
258
+ const lo_ = bus.read(regs.pc);
259
+ const hi_ = bus.read(u16(regs.pc + 1));
260
+ regs.pc = u16(regs.pc + 2);
261
+ regs.a = bus.read(toWord(hi_, lo_));
262
+ return 13;
263
+ });
264
+ decoder.register(50, (regs, _flags, bus) => {
265
+ const lo_ = bus.read(regs.pc);
266
+ const hi_ = bus.read(u16(regs.pc + 1));
267
+ regs.pc = u16(regs.pc + 2);
268
+ bus.write(toWord(hi_, lo_), regs.a);
269
+ return 13;
270
+ });
271
+ decoder.register(42, (regs, _flags, bus) => {
272
+ const lo_ = bus.read(regs.pc);
273
+ const hi_ = bus.read(u16(regs.pc + 1));
274
+ regs.pc = u16(regs.pc + 2);
275
+ const addr = toWord(hi_, lo_);
276
+ regs.l = bus.read(addr);
277
+ regs.h = bus.read(u16(addr + 1));
278
+ return 16;
279
+ });
280
+ decoder.register(34, (regs, _flags, bus) => {
281
+ const lo_ = bus.read(regs.pc);
282
+ const hi_ = bus.read(u16(regs.pc + 1));
283
+ regs.pc = u16(regs.pc + 2);
284
+ const addr = toWord(hi_, lo_);
285
+ bus.write(addr, regs.l);
286
+ bus.write(u16(addr + 1), regs.h);
287
+ return 16;
288
+ });
289
+ decoder.register(10, (regs, _flags, bus) => {
290
+ regs.a = bus.read(regs.bc);
291
+ return 7;
292
+ });
293
+ decoder.register(26, (regs, _flags, bus) => {
294
+ regs.a = bus.read(regs.de);
295
+ return 7;
296
+ });
297
+ decoder.register(2, (regs, _flags, bus) => {
298
+ bus.write(regs.bc, regs.a);
299
+ return 7;
300
+ });
301
+ decoder.register(18, (regs, _flags, bus) => {
302
+ bus.write(regs.de, regs.a);
303
+ return 7;
304
+ });
305
+ decoder.register(235, (regs, _flags, _bus) => {
306
+ const tmp = regs.hl;
307
+ regs.hl = regs.de;
308
+ regs.de = tmp;
309
+ return 5;
310
+ });
311
+ }
312
+
313
+ // src/cpu/instructions/alu.ts
314
+ var REG_ORDER2 = ["b", "c", "d", "e", "h", "l", "M", "a"];
315
+ function getReg2(regs, r, bus) {
316
+ if (r === "M") return bus.read(regs.hl);
317
+ return regs[r];
318
+ }
319
+ function setArithFlags(flags, result, ac) {
320
+ const r8 = u8(result);
321
+ flags.s = signBit(r8);
322
+ flags.z = zeroFlag(r8);
323
+ flags.ac = ac;
324
+ flags.p = parityFlag(r8);
325
+ }
326
+ function registerAlu(decoder) {
327
+ for (let r = 0; r < 8; r++) {
328
+ const opcode = 128 | r;
329
+ const reg = REG_ORDER2[r];
330
+ const cycles = reg === "M" ? 7 : 4;
331
+ decoder.register(opcode, (regs, flags, bus) => {
332
+ const a = regs.a;
333
+ const b = getReg2(regs, reg, bus);
334
+ const result = a + b;
335
+ flags.cy = result > 255;
336
+ flags.ac = auxCarryAdd(a, b);
337
+ setArithFlags(flags, result, flags.ac);
338
+ regs.a = u8(result);
339
+ return cycles;
340
+ });
341
+ }
342
+ for (let r = 0; r < 8; r++) {
343
+ const opcode = 136 | r;
344
+ const reg = REG_ORDER2[r];
345
+ const cycles = reg === "M" ? 7 : 4;
346
+ decoder.register(opcode, (regs, flags, bus) => {
347
+ const a = regs.a;
348
+ const b = getReg2(regs, reg, bus);
349
+ const cy = flags.cy ? 1 : 0;
350
+ const result = a + b + cy;
351
+ flags.cy = result > 255;
352
+ flags.ac = auxCarryAdd(a, b, cy);
353
+ setArithFlags(flags, result, flags.ac);
354
+ regs.a = u8(result);
355
+ return cycles;
356
+ });
357
+ }
358
+ for (let r = 0; r < 8; r++) {
359
+ const opcode = 144 | r;
360
+ const reg = REG_ORDER2[r];
361
+ const cycles = reg === "M" ? 7 : 4;
362
+ decoder.register(opcode, (regs, flags, bus) => {
363
+ const a = regs.a;
364
+ const b = getReg2(regs, reg, bus);
365
+ const result = a - b;
366
+ flags.cy = result < 0;
367
+ flags.ac = auxCarrySub(a, b);
368
+ setArithFlags(flags, result, flags.ac);
369
+ regs.a = u8(result);
370
+ return cycles;
371
+ });
372
+ }
373
+ for (let r = 0; r < 8; r++) {
374
+ const opcode = 152 | r;
375
+ const reg = REG_ORDER2[r];
376
+ const cycles = reg === "M" ? 7 : 4;
377
+ decoder.register(opcode, (regs, flags, bus) => {
378
+ const a = regs.a;
379
+ const b = getReg2(regs, reg, bus);
380
+ const borrow = flags.cy ? 1 : 0;
381
+ const result = a - b - borrow;
382
+ flags.cy = result < 0;
383
+ flags.ac = auxCarrySub(a, b, borrow);
384
+ setArithFlags(flags, result, flags.ac);
385
+ regs.a = u8(result);
386
+ return cycles;
387
+ });
388
+ }
389
+ decoder.register(198, (regs, flags, bus) => {
390
+ const a = regs.a;
391
+ const b = bus.read(regs.pc);
392
+ regs.pc = u16(regs.pc + 1);
393
+ const result = a + b;
394
+ flags.cy = result > 255;
395
+ flags.ac = auxCarryAdd(a, b);
396
+ setArithFlags(flags, result, flags.ac);
397
+ regs.a = u8(result);
398
+ return 7;
399
+ });
400
+ decoder.register(206, (regs, flags, bus) => {
401
+ const a = regs.a;
402
+ const b = bus.read(regs.pc);
403
+ regs.pc = u16(regs.pc + 1);
404
+ const cy = flags.cy ? 1 : 0;
405
+ const result = a + b + cy;
406
+ flags.cy = result > 255;
407
+ flags.ac = auxCarryAdd(a, b, cy);
408
+ setArithFlags(flags, result, flags.ac);
409
+ regs.a = u8(result);
410
+ return 7;
411
+ });
412
+ decoder.register(214, (regs, flags, bus) => {
413
+ const a = regs.a;
414
+ const b = bus.read(regs.pc);
415
+ regs.pc = u16(regs.pc + 1);
416
+ const result = a - b;
417
+ flags.cy = result < 0;
418
+ flags.ac = auxCarrySub(a, b);
419
+ setArithFlags(flags, result, flags.ac);
420
+ regs.a = u8(result);
421
+ return 7;
422
+ });
423
+ decoder.register(222, (regs, flags, bus) => {
424
+ const a = regs.a;
425
+ const b = bus.read(regs.pc);
426
+ regs.pc = u16(regs.pc + 1);
427
+ const borrow = flags.cy ? 1 : 0;
428
+ const result = a - b - borrow;
429
+ flags.cy = result < 0;
430
+ flags.ac = auxCarrySub(a, b, borrow);
431
+ setArithFlags(flags, result, flags.ac);
432
+ regs.a = u8(result);
433
+ return 7;
434
+ });
435
+ for (let r = 0; r < 8; r++) {
436
+ const opcode = 4 | r << 3;
437
+ const reg = REG_ORDER2[r];
438
+ const cycles = reg === "M" ? 10 : 5;
439
+ decoder.register(opcode, (regs, flags, bus) => {
440
+ const old = getReg2(regs, reg, bus);
441
+ const result = old + 1;
442
+ flags.ac = auxCarryAdd(old, 1);
443
+ flags.s = signBit(u8(result));
444
+ flags.z = zeroFlag(u8(result));
445
+ flags.p = parityFlag(u8(result));
446
+ if (reg === "M") {
447
+ bus.write(regs.hl, u8(result));
448
+ } else {
449
+ regs[reg] = u8(result);
450
+ }
451
+ return cycles;
452
+ });
453
+ }
454
+ for (let r = 0; r < 8; r++) {
455
+ const opcode = 5 | r << 3;
456
+ const reg = REG_ORDER2[r];
457
+ const cycles = reg === "M" ? 10 : 5;
458
+ decoder.register(opcode, (regs, flags, bus) => {
459
+ const old = getReg2(regs, reg, bus);
460
+ const result = old - 1;
461
+ flags.ac = auxCarrySub(old, 1);
462
+ flags.s = signBit(u8(result));
463
+ flags.z = zeroFlag(u8(result));
464
+ flags.p = parityFlag(u8(result));
465
+ if (reg === "M") {
466
+ bus.write(regs.hl, u8(result));
467
+ } else {
468
+ regs[reg] = u8(result);
469
+ }
470
+ return cycles;
471
+ });
472
+ }
473
+ decoder.register(3, (regs, _flags, _bus) => {
474
+ regs.bc = u16(regs.bc + 1);
475
+ return 5;
476
+ });
477
+ decoder.register(19, (regs, _flags, _bus) => {
478
+ regs.de = u16(regs.de + 1);
479
+ return 5;
480
+ });
481
+ decoder.register(35, (regs, _flags, _bus) => {
482
+ regs.hl = u16(regs.hl + 1);
483
+ return 5;
484
+ });
485
+ decoder.register(51, (regs, _flags, _bus) => {
486
+ regs.sp = u16(regs.sp + 1);
487
+ return 5;
488
+ });
489
+ decoder.register(11, (regs, _flags, _bus) => {
490
+ regs.bc = u16(regs.bc - 1);
491
+ return 5;
492
+ });
493
+ decoder.register(27, (regs, _flags, _bus) => {
494
+ regs.de = u16(regs.de - 1);
495
+ return 5;
496
+ });
497
+ decoder.register(43, (regs, _flags, _bus) => {
498
+ regs.hl = u16(regs.hl - 1);
499
+ return 5;
500
+ });
501
+ decoder.register(59, (regs, _flags, _bus) => {
502
+ regs.sp = u16(regs.sp - 1);
503
+ return 5;
504
+ });
505
+ function dad(regs, flags, rp) {
506
+ const result = regs.hl + rp;
507
+ flags.cy = result > 65535;
508
+ regs.hl = u16(result);
509
+ return 10;
510
+ }
511
+ decoder.register(9, (regs, flags, _bus) => dad(regs, flags, regs.bc));
512
+ decoder.register(25, (regs, flags, _bus) => dad(regs, flags, regs.de));
513
+ decoder.register(41, (regs, flags, _bus) => dad(regs, flags, regs.hl));
514
+ decoder.register(57, (regs, flags, _bus) => dad(regs, flags, regs.sp));
515
+ decoder.register(39, (regs, flags, _bus) => {
516
+ let a = regs.a;
517
+ let correction = 0;
518
+ let setCY = false;
519
+ if (flags.ac || (a & 15) > 9) {
520
+ correction |= 6;
521
+ }
522
+ if (flags.cy || a > 153) {
523
+ correction |= 96;
524
+ setCY = true;
525
+ }
526
+ const result = a + correction;
527
+ flags.ac = auxCarryAdd(a, correction);
528
+ flags.cy = setCY;
529
+ a = u8(result);
530
+ flags.s = signBit(a);
531
+ flags.z = zeroFlag(a);
532
+ flags.p = parityFlag(a);
533
+ regs.a = a;
534
+ return 4;
535
+ });
536
+ }
537
+
538
+ // src/cpu/instructions/logical.ts
539
+ var REG_ORDER3 = ["b", "c", "d", "e", "h", "l", "M", "a"];
540
+ function getReg3(regs, r, bus) {
541
+ if (r === "M") return bus.read(regs.hl);
542
+ return regs[r];
543
+ }
544
+ function setLogicFlags(flags, result) {
545
+ const r8 = u8(result);
546
+ flags.s = signBit(r8);
547
+ flags.z = zeroFlag(r8);
548
+ flags.p = parityFlag(r8);
549
+ flags.cy = false;
550
+ }
551
+ function registerLogical(decoder) {
552
+ for (let r = 0; r < 8; r++) {
553
+ const opcode = 160 | r;
554
+ const reg = REG_ORDER3[r];
555
+ const cycles = reg === "M" ? 7 : 4;
556
+ decoder.register(opcode, (regs, flags, bus) => {
557
+ const result = regs.a & getReg3(regs, reg, bus);
558
+ flags.ac = ((regs.a | getReg3(regs, reg, bus)) & 8) !== 0;
559
+ setLogicFlags(flags, result);
560
+ regs.a = u8(result);
561
+ return cycles;
562
+ });
563
+ }
564
+ decoder.register(230, (regs, flags, bus) => {
565
+ const imm = bus.read(regs.pc);
566
+ regs.pc = u16(regs.pc + 1);
567
+ flags.ac = ((regs.a | imm) & 8) !== 0;
568
+ const result = regs.a & imm;
569
+ setLogicFlags(flags, result);
570
+ regs.a = u8(result);
571
+ return 7;
572
+ });
573
+ for (let r = 0; r < 8; r++) {
574
+ const opcode = 176 | r;
575
+ const reg = REG_ORDER3[r];
576
+ const cycles = reg === "M" ? 7 : 4;
577
+ decoder.register(opcode, (regs, flags, bus) => {
578
+ const result = regs.a | getReg3(regs, reg, bus);
579
+ flags.ac = false;
580
+ setLogicFlags(flags, result);
581
+ regs.a = u8(result);
582
+ return cycles;
583
+ });
584
+ }
585
+ decoder.register(246, (regs, flags, bus) => {
586
+ const imm = bus.read(regs.pc);
587
+ regs.pc = u16(regs.pc + 1);
588
+ flags.ac = false;
589
+ const result = regs.a | imm;
590
+ setLogicFlags(flags, result);
591
+ regs.a = u8(result);
592
+ return 7;
593
+ });
594
+ for (let r = 0; r < 8; r++) {
595
+ const opcode = 168 | r;
596
+ const reg = REG_ORDER3[r];
597
+ const cycles = reg === "M" ? 7 : 4;
598
+ decoder.register(opcode, (regs, flags, bus) => {
599
+ const result = regs.a ^ getReg3(regs, reg, bus);
600
+ flags.ac = false;
601
+ setLogicFlags(flags, result);
602
+ regs.a = u8(result);
603
+ return cycles;
604
+ });
605
+ }
606
+ decoder.register(238, (regs, flags, bus) => {
607
+ const imm = bus.read(regs.pc);
608
+ regs.pc = u16(regs.pc + 1);
609
+ flags.ac = false;
610
+ const result = regs.a ^ imm;
611
+ setLogicFlags(flags, result);
612
+ regs.a = u8(result);
613
+ return 7;
614
+ });
615
+ for (let r = 0; r < 8; r++) {
616
+ const opcode = 184 | r;
617
+ const reg = REG_ORDER3[r];
618
+ const cycles = reg === "M" ? 7 : 4;
619
+ decoder.register(opcode, (regs, flags, bus) => {
620
+ const a = regs.a;
621
+ const b = getReg3(regs, reg, bus);
622
+ const result = a - b;
623
+ flags.cy = result < 0;
624
+ flags.ac = (a & 15) - (b & 15) < 0;
625
+ setLogicFlags(flags, result);
626
+ flags.cy = a < b;
627
+ return cycles;
628
+ });
629
+ }
630
+ decoder.register(254, (regs, flags, bus) => {
631
+ const a = regs.a;
632
+ const b = bus.read(regs.pc);
633
+ regs.pc = u16(regs.pc + 1);
634
+ const result = a - b;
635
+ flags.cy = a < b;
636
+ flags.ac = (a & 15) - (b & 15) < 0;
637
+ setLogicFlags(flags, result);
638
+ flags.cy = a < b;
639
+ return 7;
640
+ });
641
+ decoder.register(47, (regs, _flags, _bus) => {
642
+ regs.a = u8(~regs.a);
643
+ return 4;
644
+ });
645
+ decoder.register(63, (_regs, flags, _bus) => {
646
+ flags.cy = !flags.cy;
647
+ return 4;
648
+ });
649
+ decoder.register(55, (_regs, flags, _bus) => {
650
+ flags.cy = true;
651
+ return 4;
652
+ });
653
+ }
654
+
655
+ // src/cpu/instructions/rotate.ts
656
+ function registerRotate(decoder) {
657
+ decoder.register(7, (regs, flags, _bus) => {
658
+ const a = regs.a;
659
+ flags.cy = (a & 128) !== 0;
660
+ regs.a = u8(a << 1 | (flags.cy ? 1 : 0));
661
+ return 4;
662
+ });
663
+ decoder.register(15, (regs, flags, _bus) => {
664
+ const a = regs.a;
665
+ flags.cy = (a & 1) !== 0;
666
+ regs.a = u8(a >> 1 | (flags.cy ? 128 : 0));
667
+ return 4;
668
+ });
669
+ decoder.register(23, (regs, flags, _bus) => {
670
+ const a = regs.a;
671
+ const newCY = (a & 128) !== 0;
672
+ regs.a = u8(a << 1 | (flags.cy ? 1 : 0));
673
+ flags.cy = newCY;
674
+ return 4;
675
+ });
676
+ decoder.register(31, (regs, flags, _bus) => {
677
+ const a = regs.a;
678
+ const newCY = (a & 1) !== 0;
679
+ regs.a = u8(a >> 1 | (flags.cy ? 128 : 0));
680
+ flags.cy = newCY;
681
+ return 4;
682
+ });
683
+ }
684
+
685
+ // src/cpu/instructions/branch.ts
686
+ function readAddr(regs, bus) {
687
+ const lo2 = bus.read(regs.pc);
688
+ const hi2 = bus.read(u16(regs.pc + 1));
689
+ regs.pc = u16(regs.pc + 2);
690
+ return toWord(hi2, lo2);
691
+ }
692
+ function registerBranch(decoder) {
693
+ decoder.register(195, (regs, _flags, bus) => {
694
+ regs.pc = readAddr(regs, bus);
695
+ return 10;
696
+ });
697
+ const jccTable = [
698
+ [194, (f) => !f.z],
699
+ // JNZ
700
+ [202, (f) => f.z],
701
+ // JZ
702
+ [210, (f) => !f.cy],
703
+ // JNC
704
+ [218, (f) => f.cy],
705
+ // JC
706
+ [226, (f) => !f.p],
707
+ // JPO (parity odd)
708
+ [234, (f) => f.p],
709
+ // JPE (parity even)
710
+ [242, (f) => !f.s],
711
+ // JP (positive)
712
+ [250, (f) => f.s]
713
+ // JM (minus)
714
+ ];
715
+ for (const [opcode, cond2] of jccTable) {
716
+ decoder.register(opcode, (regs, flags, bus) => {
717
+ const addr = readAddr(regs, bus);
718
+ if (cond2(flags)) regs.pc = addr;
719
+ return 10;
720
+ });
721
+ }
722
+ decoder.register(205, (regs, _flags, bus) => {
723
+ const addr = readAddr(regs, bus);
724
+ regs.sp = u16(regs.sp - 1);
725
+ bus.write(regs.sp, regs.pc >> 8 & 255);
726
+ regs.sp = u16(regs.sp - 1);
727
+ bus.write(regs.sp, regs.pc & 255);
728
+ regs.pc = addr;
729
+ return 17;
730
+ });
731
+ const callTable = [
732
+ [196, (f) => !f.z],
733
+ // CNZ
734
+ [204, (f) => f.z],
735
+ // CZ
736
+ [212, (f) => !f.cy],
737
+ // CNC
738
+ [220, (f) => f.cy],
739
+ // CC
740
+ [228, (f) => !f.p],
741
+ // CPO
742
+ [236, (f) => f.p],
743
+ // CPE
744
+ [244, (f) => !f.s],
745
+ // CP
746
+ [252, (f) => f.s]
747
+ // CM
748
+ ];
749
+ for (const [opcode, cond2] of callTable) {
750
+ decoder.register(opcode, (regs, flags, bus) => {
751
+ const addr = readAddr(regs, bus);
752
+ if (cond2(flags)) {
753
+ regs.sp = u16(regs.sp - 1);
754
+ bus.write(regs.sp, regs.pc >> 8 & 255);
755
+ regs.sp = u16(regs.sp - 1);
756
+ bus.write(regs.sp, regs.pc & 255);
757
+ regs.pc = addr;
758
+ return 17;
759
+ }
760
+ return 11;
761
+ });
762
+ }
763
+ decoder.register(201, (regs, _flags, bus) => {
764
+ const lo2 = bus.read(regs.sp);
765
+ const hi2 = bus.read(u16(regs.sp + 1));
766
+ regs.sp = u16(regs.sp + 2);
767
+ regs.pc = toWord(hi2, lo2);
768
+ return 10;
769
+ });
770
+ const retTable = [
771
+ [192, (f) => !f.z],
772
+ // RNZ
773
+ [200, (f) => f.z],
774
+ // RZ
775
+ [208, (f) => !f.cy],
776
+ // RNC
777
+ [216, (f) => f.cy],
778
+ // RC
779
+ [224, (f) => !f.p],
780
+ // RPO
781
+ [232, (f) => f.p],
782
+ // RPE
783
+ [240, (f) => !f.s],
784
+ // RP
785
+ [248, (f) => f.s]
786
+ // RM
787
+ ];
788
+ for (const [opcode, cond2] of retTable) {
789
+ decoder.register(opcode, (regs, flags, bus) => {
790
+ if (cond2(flags)) {
791
+ const lo2 = bus.read(regs.sp);
792
+ const hi2 = bus.read(u16(regs.sp + 1));
793
+ regs.sp = u16(regs.sp + 2);
794
+ regs.pc = toWord(hi2, lo2);
795
+ return 11;
796
+ }
797
+ return 5;
798
+ });
799
+ }
800
+ for (let n = 0; n < 8; n++) {
801
+ const opcode = 199 | n << 3;
802
+ decoder.register(opcode, (regs, _flags, bus) => {
803
+ regs.sp = u16(regs.sp - 1);
804
+ bus.write(regs.sp, regs.pc >> 8 & 255);
805
+ regs.sp = u16(regs.sp - 1);
806
+ bus.write(regs.sp, regs.pc & 255);
807
+ regs.pc = n * 8;
808
+ return 11;
809
+ });
810
+ }
811
+ decoder.register(233, (regs, _flags, _bus) => {
812
+ regs.pc = regs.hl;
813
+ return 5;
814
+ });
815
+ }
816
+
817
+ // src/cpu/instructions/stack.ts
818
+ function registerStack(decoder) {
819
+ decoder.register(197, (regs, _flags, bus) => {
820
+ regs.sp = u16(regs.sp - 1);
821
+ bus.write(regs.sp, regs.b);
822
+ regs.sp = u16(regs.sp - 1);
823
+ bus.write(regs.sp, regs.c);
824
+ return 11;
825
+ });
826
+ decoder.register(213, (regs, _flags, bus) => {
827
+ regs.sp = u16(regs.sp - 1);
828
+ bus.write(regs.sp, regs.d);
829
+ regs.sp = u16(regs.sp - 1);
830
+ bus.write(regs.sp, regs.e);
831
+ return 11;
832
+ });
833
+ decoder.register(229, (regs, _flags, bus) => {
834
+ regs.sp = u16(regs.sp - 1);
835
+ bus.write(regs.sp, regs.h);
836
+ regs.sp = u16(regs.sp - 1);
837
+ bus.write(regs.sp, regs.l);
838
+ return 11;
839
+ });
840
+ decoder.register(245, (regs, flags, bus) => {
841
+ regs.sp = u16(regs.sp - 1);
842
+ bus.write(regs.sp, regs.a);
843
+ regs.sp = u16(regs.sp - 1);
844
+ bus.write(regs.sp, flags.toByte());
845
+ return 11;
846
+ });
847
+ decoder.register(193, (regs, _flags, bus) => {
848
+ regs.c = bus.read(regs.sp);
849
+ regs.sp = u16(regs.sp + 1);
850
+ regs.b = bus.read(regs.sp);
851
+ regs.sp = u16(regs.sp + 1);
852
+ return 10;
853
+ });
854
+ decoder.register(209, (regs, _flags, bus) => {
855
+ regs.e = bus.read(regs.sp);
856
+ regs.sp = u16(regs.sp + 1);
857
+ regs.d = bus.read(regs.sp);
858
+ regs.sp = u16(regs.sp + 1);
859
+ return 10;
860
+ });
861
+ decoder.register(225, (regs, _flags, bus) => {
862
+ regs.l = bus.read(regs.sp);
863
+ regs.sp = u16(regs.sp + 1);
864
+ regs.h = bus.read(regs.sp);
865
+ regs.sp = u16(regs.sp + 1);
866
+ return 10;
867
+ });
868
+ decoder.register(241, (regs, flags, bus) => {
869
+ const psw = bus.read(regs.sp);
870
+ regs.sp = u16(regs.sp + 1);
871
+ regs.a = bus.read(regs.sp);
872
+ regs.sp = u16(regs.sp + 1);
873
+ flags.fromByte(psw);
874
+ return 10;
875
+ });
876
+ decoder.register(227, (regs, _flags, bus) => {
877
+ const lo2 = bus.read(regs.sp);
878
+ const hi2 = bus.read(u16(regs.sp + 1));
879
+ bus.write(regs.sp, regs.l);
880
+ bus.write(u16(regs.sp + 1), regs.h);
881
+ regs.l = lo2;
882
+ regs.h = hi2;
883
+ return 18;
884
+ });
885
+ decoder.register(249, (regs, _flags, _bus) => {
886
+ regs.sp = regs.hl;
887
+ return 5;
888
+ });
889
+ }
890
+
891
+ // src/cpu/instructions/io.ts
892
+ function registerIO(decoder) {
893
+ decoder.register(219, (regs, _flags, bus) => {
894
+ const port = bus.read(regs.pc);
895
+ regs.pc = u16(regs.pc + 1);
896
+ regs.a = bus.ioRead(port);
897
+ return 10;
898
+ });
899
+ decoder.register(211, (regs, _flags, bus) => {
900
+ const port = bus.read(regs.pc);
901
+ regs.pc = u16(regs.pc + 1);
902
+ bus.ioWrite(port, regs.a);
903
+ return 10;
904
+ });
905
+ }
906
+
907
+ // src/cpu/Cpu8080.ts
908
+ var Cpu8080 = class {
909
+ registers;
910
+ flags;
911
+ bus;
912
+ pic;
913
+ decoder;
914
+ inte = false;
915
+ pendingEI = false;
916
+ halted = false;
917
+ /** Front-panel status byte of the last instruction (Bitsby8 cockpit). */
918
+ lastStatus = FETCH_STATUS;
919
+ statusTable = buildStatusTable();
920
+ /** Program counter accessor (ICpu); proxies the register file. */
921
+ get pc() {
922
+ return this.registers.pc;
923
+ }
924
+ set pc(v) {
925
+ this.registers.pc = u16(v);
926
+ }
927
+ /** Uniform register/flags snapshot for introspection (ICpu). */
928
+ state() {
929
+ const r = this.registers;
930
+ return { pc: r.pc, sp: r.sp, a: r.a, f: this.flags.toByte(), b: r.b, c: r.c, d: r.d, e: r.e, h: r.h, l: r.l, halted: this.halted, inte: this.inte, intPending: this.pic.hasPendingInterrupt(), status: this.lastStatus };
931
+ }
932
+ constructor(bus, pic) {
933
+ this.bus = bus;
934
+ this.pic = pic;
935
+ this.registers = new Registers();
936
+ this.flags = new Flags();
937
+ this.decoder = new Decoder();
938
+ this.buildDecoder();
939
+ }
940
+ buildDecoder() {
941
+ registerControl(this.decoder);
942
+ registerData(this.decoder);
943
+ registerAlu(this.decoder);
944
+ registerLogical(this.decoder);
945
+ registerRotate(this.decoder);
946
+ registerBranch(this.decoder);
947
+ registerStack(this.decoder);
948
+ registerIO(this.decoder);
949
+ }
950
+ reset() {
951
+ this.registers.reset();
952
+ this.flags.reset();
953
+ this.inte = false;
954
+ this.pendingEI = false;
955
+ this.halted = false;
956
+ this.lastStatus = FETCH_STATUS;
957
+ }
958
+ step() {
959
+ const regs = this.registers;
960
+ if (this.halted) {
961
+ if (this.inte && this.pic.hasPendingInterrupt()) {
962
+ this.handleInterrupt();
963
+ return 11;
964
+ }
965
+ return 4;
966
+ }
967
+ if (this.pendingEI) {
968
+ this.inte = true;
969
+ this.pendingEI = false;
970
+ }
971
+ if (this.inte && this.pic.hasPendingInterrupt()) {
972
+ this.handleInterrupt();
973
+ return 11;
974
+ }
975
+ const opcode = this.bus.read(regs.pc);
976
+ regs.pc = u16(regs.pc + 1);
977
+ this.lastStatus = this.statusTable[opcode];
978
+ if (opcode === 118) {
979
+ this.halted = true;
980
+ return 7;
981
+ }
982
+ if (opcode === 251) {
983
+ this.pendingEI = true;
984
+ return 4;
985
+ }
986
+ if (opcode === 243) {
987
+ this.inte = false;
988
+ this.pendingEI = false;
989
+ return 4;
990
+ }
991
+ const handler = this.decoder.decode(opcode);
992
+ return handler(regs, this.flags, this.bus);
993
+ }
994
+ handleInterrupt() {
995
+ this.inte = false;
996
+ this.halted = false;
997
+ this.lastStatus = INTA_STATUS;
998
+ const rstByte = this.bus.acknowledgeInterrupt();
999
+ const regs = this.registers;
1000
+ regs.sp = u16(regs.sp - 1);
1001
+ this.bus.write(regs.sp, regs.pc >> 8 & 255);
1002
+ regs.sp = u16(regs.sp - 1);
1003
+ this.bus.write(regs.sp, regs.pc & 255);
1004
+ regs.pc = rstByte & 56;
1005
+ }
1006
+ /**
1007
+ * Run until halted or maxCycles exceeded.
1008
+ * Returns total T-states executed.
1009
+ */
1010
+ run(maxCycles = Infinity) {
1011
+ let total = 0n;
1012
+ while (!this.halted && total < BigInt(maxCycles)) {
1013
+ total += BigInt(this.step());
1014
+ }
1015
+ return total;
1016
+ }
1017
+ };
1018
+
1019
+ // src/cpu/z80/RegistersZ80.ts
1020
+ var RegistersZ80 = class {
1021
+ a = 0;
1022
+ b = 0;
1023
+ c = 0;
1024
+ d = 0;
1025
+ e = 0;
1026
+ h = 0;
1027
+ l = 0;
1028
+ // Shadow set (swapped by EXX / EX AF,AF'). F' is a raw PSW byte.
1029
+ a2 = 0;
1030
+ f2 = 0;
1031
+ b2 = 0;
1032
+ c2 = 0;
1033
+ d2 = 0;
1034
+ e2 = 0;
1035
+ h2 = 0;
1036
+ l2 = 0;
1037
+ ix = 0;
1038
+ iy = 0;
1039
+ sp = 0;
1040
+ pc = 0;
1041
+ i = 0;
1042
+ // interrupt vector base
1043
+ r = 0;
1044
+ // refresh counter (bit 7 preserved across increments)
1045
+ wz = 0;
1046
+ // MEMPTR — internal 16-bit temp
1047
+ /** BC register pair */
1048
+ get bc() {
1049
+ return this.b << 8 | this.c;
1050
+ }
1051
+ set bc(v) {
1052
+ this.b = v >> 8 & 255;
1053
+ this.c = v & 255;
1054
+ }
1055
+ /** DE register pair */
1056
+ get de() {
1057
+ return this.d << 8 | this.e;
1058
+ }
1059
+ set de(v) {
1060
+ this.d = v >> 8 & 255;
1061
+ this.e = v & 255;
1062
+ }
1063
+ /** HL register pair */
1064
+ get hl() {
1065
+ return this.h << 8 | this.l;
1066
+ }
1067
+ set hl(v) {
1068
+ this.h = v >> 8 & 255;
1069
+ this.l = v & 255;
1070
+ }
1071
+ /** IX high/low bytes (undocumented IXH/IXL) */
1072
+ get ixh() {
1073
+ return this.ix >> 8 & 255;
1074
+ }
1075
+ set ixh(v) {
1076
+ this.ix = (v & 255) << 8 | this.ix & 255;
1077
+ }
1078
+ get ixl() {
1079
+ return this.ix & 255;
1080
+ }
1081
+ set ixl(v) {
1082
+ this.ix = this.ix & 65280 | v & 255;
1083
+ }
1084
+ /** IY high/low bytes (undocumented IYH/IYL) */
1085
+ get iyh() {
1086
+ return this.iy >> 8 & 255;
1087
+ }
1088
+ set iyh(v) {
1089
+ this.iy = (v & 255) << 8 | this.iy & 255;
1090
+ }
1091
+ get iyl() {
1092
+ return this.iy & 255;
1093
+ }
1094
+ set iyl(v) {
1095
+ this.iy = this.iy & 65280 | v & 255;
1096
+ }
1097
+ /** M1 refresh tick: low 7 bits increment, bit 7 preserved. */
1098
+ incR() {
1099
+ this.r = this.r & 128 | this.r + 1 & 127;
1100
+ }
1101
+ /** EXX: swap BC/DE/HL with their shadows (AF is separate, via EX AF,AF'). */
1102
+ exx() {
1103
+ let t = this.b;
1104
+ this.b = this.b2;
1105
+ this.b2 = t;
1106
+ t = this.c;
1107
+ this.c = this.c2;
1108
+ this.c2 = t;
1109
+ t = this.d;
1110
+ this.d = this.d2;
1111
+ this.d2 = t;
1112
+ t = this.e;
1113
+ this.e = this.e2;
1114
+ this.e2 = t;
1115
+ t = this.h;
1116
+ this.h = this.h2;
1117
+ this.h2 = t;
1118
+ t = this.l;
1119
+ this.l = this.l2;
1120
+ this.l2 = t;
1121
+ }
1122
+ reset() {
1123
+ this.a = this.b = this.c = this.d = this.e = this.h = this.l = 0;
1124
+ this.a2 = this.f2 = this.b2 = this.c2 = this.d2 = this.e2 = this.h2 = this.l2 = 0;
1125
+ this.ix = this.iy = 0;
1126
+ this.sp = 0;
1127
+ this.pc = 0;
1128
+ this.i = this.r = 0;
1129
+ this.wz = 0;
1130
+ }
1131
+ };
1132
+
1133
+ // src/cpu/z80/FlagsZ80.ts
1134
+ var FlagsZ80 = class {
1135
+ s = false;
1136
+ z = false;
1137
+ y = false;
1138
+ h = false;
1139
+ x = false;
1140
+ pv = false;
1141
+ n = false;
1142
+ c = false;
1143
+ toByte() {
1144
+ return (this.s ? 128 : 0) | (this.z ? 64 : 0) | (this.y ? 32 : 0) | (this.h ? 16 : 0) | (this.x ? 8 : 0) | (this.pv ? 4 : 0) | (this.n ? 2 : 0) | (this.c ? 1 : 0);
1145
+ }
1146
+ fromByte(b) {
1147
+ this.s = (b & 128) !== 0;
1148
+ this.z = (b & 64) !== 0;
1149
+ this.y = (b & 32) !== 0;
1150
+ this.h = (b & 16) !== 0;
1151
+ this.x = (b & 8) !== 0;
1152
+ this.pv = (b & 4) !== 0;
1153
+ this.n = (b & 2) !== 0;
1154
+ this.c = (b & 1) !== 0;
1155
+ }
1156
+ reset() {
1157
+ this.s = this.z = this.y = this.h = this.x = this.pv = this.n = this.c = false;
1158
+ }
1159
+ };
1160
+
1161
+ // src/cpu/z80/views.ts
1162
+ function sext8(b) {
1163
+ return (b & 128) !== 0 ? (b & 255) - 256 : b & 255;
1164
+ }
1165
+ var HL_VIEW = {
1166
+ kind: "hl",
1167
+ indexed: false,
1168
+ memExtra: 0,
1169
+ getPair: (r) => r.hl,
1170
+ setPair: (r, v) => {
1171
+ r.hl = v & 65535;
1172
+ },
1173
+ getHi: (r) => r.h,
1174
+ setHi: (r, v) => {
1175
+ r.h = v & 255;
1176
+ },
1177
+ getLo: (r) => r.l,
1178
+ setLo: (r, v) => {
1179
+ r.l = v & 255;
1180
+ },
1181
+ // Plain (HL) access does not touch WZ.
1182
+ memAddr: (cpu) => cpu.regs.hl
1183
+ };
1184
+ function makeIndexView(kind, getReg4, setReg2, getH, setH, getL, setL) {
1185
+ return {
1186
+ kind,
1187
+ indexed: true,
1188
+ memExtra: 8,
1189
+ // 3 T to read d + 5 T internal add
1190
+ getPair: getReg4,
1191
+ setPair: setReg2,
1192
+ getHi: getH,
1193
+ setHi: setH,
1194
+ getLo: getL,
1195
+ setLo: setL,
1196
+ memAddr: (cpu) => {
1197
+ const d = sext8(cpu.fetchByte());
1198
+ const addr = getReg4(cpu.regs) + d & 65535;
1199
+ cpu.regs.wz = addr;
1200
+ return addr;
1201
+ }
1202
+ };
1203
+ }
1204
+ var IX_VIEW = makeIndexView(
1205
+ "ix",
1206
+ (r) => r.ix,
1207
+ (r, v) => {
1208
+ r.ix = v & 65535;
1209
+ },
1210
+ (r) => r.ixh,
1211
+ (r, v) => {
1212
+ r.ixh = v;
1213
+ },
1214
+ (r) => r.ixl,
1215
+ (r, v) => {
1216
+ r.ixl = v;
1217
+ }
1218
+ );
1219
+ var IY_VIEW = makeIndexView(
1220
+ "iy",
1221
+ (r) => r.iy,
1222
+ (r, v) => {
1223
+ r.iy = v & 65535;
1224
+ },
1225
+ (r) => r.iyh,
1226
+ (r, v) => {
1227
+ r.iyh = v;
1228
+ },
1229
+ (r) => r.iyl,
1230
+ (r, v) => {
1231
+ r.iyl = v;
1232
+ }
1233
+ );
1234
+
1235
+ // src/cpu/z80/regcodes.ts
1236
+ function getR(regs, view, code) {
1237
+ switch (code) {
1238
+ case 0:
1239
+ return regs.b;
1240
+ case 1:
1241
+ return regs.c;
1242
+ case 2:
1243
+ return regs.d;
1244
+ case 3:
1245
+ return regs.e;
1246
+ case 4:
1247
+ return view.getHi(regs);
1248
+ case 5:
1249
+ return view.getLo(regs);
1250
+ case 7:
1251
+ return regs.a;
1252
+ default:
1253
+ return 0;
1254
+ }
1255
+ }
1256
+ function setR(regs, view, code, v) {
1257
+ const b = v & 255;
1258
+ switch (code) {
1259
+ case 0:
1260
+ regs.b = b;
1261
+ break;
1262
+ case 1:
1263
+ regs.c = b;
1264
+ break;
1265
+ case 2:
1266
+ regs.d = b;
1267
+ break;
1268
+ case 3:
1269
+ regs.e = b;
1270
+ break;
1271
+ case 4:
1272
+ view.setHi(regs, b);
1273
+ break;
1274
+ case 5:
1275
+ view.setLo(regs, b);
1276
+ break;
1277
+ case 7:
1278
+ regs.a = b;
1279
+ break;
1280
+ }
1281
+ }
1282
+ function getRealR(regs, code) {
1283
+ switch (code) {
1284
+ case 0:
1285
+ return regs.b;
1286
+ case 1:
1287
+ return regs.c;
1288
+ case 2:
1289
+ return regs.d;
1290
+ case 3:
1291
+ return regs.e;
1292
+ case 4:
1293
+ return regs.h;
1294
+ case 5:
1295
+ return regs.l;
1296
+ case 7:
1297
+ return regs.a;
1298
+ default:
1299
+ return 0;
1300
+ }
1301
+ }
1302
+ function setRealR(regs, code, v) {
1303
+ const b = v & 255;
1304
+ switch (code) {
1305
+ case 0:
1306
+ regs.b = b;
1307
+ break;
1308
+ case 1:
1309
+ regs.c = b;
1310
+ break;
1311
+ case 2:
1312
+ regs.d = b;
1313
+ break;
1314
+ case 3:
1315
+ regs.e = b;
1316
+ break;
1317
+ case 4:
1318
+ regs.h = b;
1319
+ break;
1320
+ case 5:
1321
+ regs.l = b;
1322
+ break;
1323
+ case 7:
1324
+ regs.a = b;
1325
+ break;
1326
+ }
1327
+ }
1328
+
1329
+ // src/cpu/z80/instructions/load.ts
1330
+ function read16(cpu, addr) {
1331
+ const lo2 = cpu.bus.read(addr);
1332
+ const hi2 = cpu.bus.read(u16(addr + 1));
1333
+ return hi2 << 8 | lo2;
1334
+ }
1335
+ function write16(cpu, addr, v) {
1336
+ cpu.bus.write(addr, v & 255);
1337
+ cpu.bus.write(u16(addr + 1), v >> 8 & 255);
1338
+ }
1339
+ function registerLoad(table, view) {
1340
+ for (let op = 64; op <= 127; op++) {
1341
+ if (op === 118) continue;
1342
+ const dst = op >> 3 & 7;
1343
+ const src = op & 7;
1344
+ if (src === 6) {
1345
+ table[op] = (cpu) => {
1346
+ const addr = view.memAddr(cpu);
1347
+ setRealR(cpu.regs, dst, cpu.bus.read(addr));
1348
+ return 7 + view.memExtra;
1349
+ };
1350
+ } else if (dst === 6) {
1351
+ table[op] = (cpu) => {
1352
+ const addr = view.memAddr(cpu);
1353
+ cpu.bus.write(addr, getRealR(cpu.regs, src));
1354
+ return 7 + view.memExtra;
1355
+ };
1356
+ } else {
1357
+ table[op] = (cpu) => {
1358
+ setR(cpu.regs, view, dst, getR(cpu.regs, view, src));
1359
+ return 4;
1360
+ };
1361
+ }
1362
+ }
1363
+ for (let r = 0; r < 8; r++) {
1364
+ const op = 6 | r << 3;
1365
+ if (r === 6) {
1366
+ table[op] = (cpu) => {
1367
+ const addr = view.memAddr(cpu);
1368
+ cpu.bus.write(addr, cpu.fetchByte());
1369
+ return view.indexed ? 15 : 10;
1370
+ };
1371
+ } else {
1372
+ table[op] = (cpu) => {
1373
+ setR(cpu.regs, view, r, cpu.fetchByte());
1374
+ return 7;
1375
+ };
1376
+ }
1377
+ }
1378
+ table[10] = (cpu) => {
1379
+ const a = cpu.regs.bc;
1380
+ cpu.regs.a = cpu.bus.read(a);
1381
+ cpu.regs.wz = u16(a + 1);
1382
+ return 7;
1383
+ };
1384
+ table[26] = (cpu) => {
1385
+ const a = cpu.regs.de;
1386
+ cpu.regs.a = cpu.bus.read(a);
1387
+ cpu.regs.wz = u16(a + 1);
1388
+ return 7;
1389
+ };
1390
+ table[58] = (cpu) => {
1391
+ const nn = cpu.fetchWord();
1392
+ cpu.regs.a = cpu.bus.read(nn);
1393
+ cpu.regs.wz = u16(nn + 1);
1394
+ return 13;
1395
+ };
1396
+ table[2] = (cpu) => {
1397
+ const a = cpu.regs.bc;
1398
+ cpu.bus.write(a, cpu.regs.a);
1399
+ cpu.regs.wz = (cpu.regs.a << 8 | a + 1 & 255) & 65535;
1400
+ return 7;
1401
+ };
1402
+ table[18] = (cpu) => {
1403
+ const a = cpu.regs.de;
1404
+ cpu.bus.write(a, cpu.regs.a);
1405
+ cpu.regs.wz = (cpu.regs.a << 8 | a + 1 & 255) & 65535;
1406
+ return 7;
1407
+ };
1408
+ table[50] = (cpu) => {
1409
+ const nn = cpu.fetchWord();
1410
+ cpu.bus.write(nn, cpu.regs.a);
1411
+ cpu.regs.wz = (cpu.regs.a << 8 | nn + 1 & 255) & 65535;
1412
+ return 13;
1413
+ };
1414
+ table[1] = (cpu) => {
1415
+ cpu.regs.bc = cpu.fetchWord();
1416
+ return 10;
1417
+ };
1418
+ table[17] = (cpu) => {
1419
+ cpu.regs.de = cpu.fetchWord();
1420
+ return 10;
1421
+ };
1422
+ table[33] = (cpu) => {
1423
+ view.setPair(cpu.regs, cpu.fetchWord());
1424
+ return 10;
1425
+ };
1426
+ table[49] = (cpu) => {
1427
+ cpu.regs.sp = cpu.fetchWord();
1428
+ return 10;
1429
+ };
1430
+ table[34] = (cpu) => {
1431
+ const nn = cpu.fetchWord();
1432
+ write16(cpu, nn, view.getPair(cpu.regs));
1433
+ cpu.regs.wz = u16(nn + 1);
1434
+ return 16;
1435
+ };
1436
+ table[42] = (cpu) => {
1437
+ const nn = cpu.fetchWord();
1438
+ view.setPair(cpu.regs, read16(cpu, nn));
1439
+ cpu.regs.wz = u16(nn + 1);
1440
+ return 16;
1441
+ };
1442
+ table[249] = (cpu) => {
1443
+ cpu.regs.sp = view.getPair(cpu.regs);
1444
+ return 6;
1445
+ };
1446
+ }
1447
+
1448
+ // src/cpu/z80/instructions/exchange.ts
1449
+ function registerExchange(table, view) {
1450
+ table[235] = (cpu) => {
1451
+ const de = cpu.regs.de;
1452
+ cpu.regs.de = cpu.regs.hl;
1453
+ cpu.regs.hl = de;
1454
+ return 4;
1455
+ };
1456
+ table[8] = (cpu) => {
1457
+ const a = cpu.regs.a;
1458
+ const f = cpu.flags.toByte();
1459
+ cpu.regs.a = cpu.regs.a2;
1460
+ cpu.flags.fromByte(cpu.regs.f2);
1461
+ cpu.regs.a2 = a;
1462
+ cpu.regs.f2 = f;
1463
+ return 4;
1464
+ };
1465
+ table[217] = (cpu) => {
1466
+ cpu.regs.exx();
1467
+ return 4;
1468
+ };
1469
+ table[227] = (cpu) => {
1470
+ const sp = cpu.regs.sp;
1471
+ const lo2 = cpu.bus.read(sp);
1472
+ const hi2 = cpu.bus.read(u16(sp + 1));
1473
+ const val = view.getPair(cpu.regs);
1474
+ cpu.bus.write(sp, val & 255);
1475
+ cpu.bus.write(u16(sp + 1), val >> 8 & 255);
1476
+ const swapped = hi2 << 8 | lo2;
1477
+ view.setPair(cpu.regs, swapped);
1478
+ cpu.regs.wz = swapped;
1479
+ return 19;
1480
+ };
1481
+ }
1482
+
1483
+ // src/cpu/z80/flagHelpers.ts
1484
+ var PARITY = (() => {
1485
+ const t = new Array(256);
1486
+ for (let v = 0; v < 256; v++) {
1487
+ let x = v;
1488
+ x ^= x >> 4;
1489
+ x ^= x >> 2;
1490
+ x ^= x >> 1;
1491
+ t[v] = (x & 1) === 0;
1492
+ }
1493
+ return t;
1494
+ })();
1495
+ function parityEven(v) {
1496
+ return PARITY[v & 255];
1497
+ }
1498
+ function setSzyx(f, r) {
1499
+ f.s = (r & 128) !== 0;
1500
+ f.z = (r & 255) === 0;
1501
+ f.y = (r & 32) !== 0;
1502
+ f.x = (r & 8) !== 0;
1503
+ }
1504
+ function setSzyxp(f, r) {
1505
+ setSzyx(f, r);
1506
+ f.pv = PARITY[r & 255];
1507
+ }
1508
+ function add8(f, a, b, cIn) {
1509
+ const sum = a + b + cIn;
1510
+ const r = sum & 255;
1511
+ setSzyx(f, r);
1512
+ f.h = (a & 15) + (b & 15) + cIn > 15;
1513
+ f.pv = (~(a ^ b) & (a ^ r) & 128) !== 0;
1514
+ f.n = false;
1515
+ f.c = sum > 255;
1516
+ return r;
1517
+ }
1518
+ function sub8(f, a, b, cIn) {
1519
+ const diff = a - b - cIn;
1520
+ const r = diff & 255;
1521
+ setSzyx(f, r);
1522
+ f.h = (a & 15) - (b & 15) - cIn < 0;
1523
+ f.pv = ((a ^ b) & (a ^ r) & 128) !== 0;
1524
+ f.n = true;
1525
+ f.c = diff < 0;
1526
+ return r;
1527
+ }
1528
+ function cp8(f, a, b) {
1529
+ const diff = a - b;
1530
+ const r = diff & 255;
1531
+ f.s = (r & 128) !== 0;
1532
+ f.z = r === 0;
1533
+ f.y = (b & 32) !== 0;
1534
+ f.x = (b & 8) !== 0;
1535
+ f.h = (a & 15) - (b & 15) < 0;
1536
+ f.pv = ((a ^ b) & (a ^ r) & 128) !== 0;
1537
+ f.n = true;
1538
+ f.c = diff < 0;
1539
+ }
1540
+ function inc8(f, v) {
1541
+ const r = v + 1 & 255;
1542
+ setSzyx(f, r);
1543
+ f.h = (v & 15) === 15;
1544
+ f.pv = v === 127;
1545
+ f.n = false;
1546
+ return r;
1547
+ }
1548
+ function dec8(f, v) {
1549
+ const r = v - 1 & 255;
1550
+ setSzyx(f, r);
1551
+ f.h = (v & 15) === 0;
1552
+ f.pv = v === 128;
1553
+ f.n = true;
1554
+ return r;
1555
+ }
1556
+ function setLogicFlags2(f, r, hVal) {
1557
+ setSzyxp(f, r);
1558
+ f.h = hVal;
1559
+ f.n = false;
1560
+ f.c = false;
1561
+ }
1562
+ function add16(f, a, b) {
1563
+ const sum = a + b;
1564
+ const r = sum & 65535;
1565
+ f.h = (a & 4095) + (b & 4095) > 4095;
1566
+ f.n = false;
1567
+ f.c = sum > 65535;
1568
+ f.y = (r & 8192) !== 0;
1569
+ f.x = (r & 2048) !== 0;
1570
+ return r;
1571
+ }
1572
+ function adc16(f, a, b, cIn) {
1573
+ const sum = a + b + cIn;
1574
+ const r = sum & 65535;
1575
+ f.s = (r & 32768) !== 0;
1576
+ f.z = r === 0;
1577
+ f.h = (a & 4095) + (b & 4095) + cIn > 4095;
1578
+ f.pv = (~(a ^ b) & (a ^ r) & 32768) !== 0;
1579
+ f.n = false;
1580
+ f.c = sum > 65535;
1581
+ f.y = (r & 8192) !== 0;
1582
+ f.x = (r & 2048) !== 0;
1583
+ return r;
1584
+ }
1585
+ function sbc16(f, a, b, cIn) {
1586
+ const diff = a - b - cIn;
1587
+ const r = diff & 65535;
1588
+ f.s = (r & 32768) !== 0;
1589
+ f.z = r === 0;
1590
+ f.h = (a & 4095) - (b & 4095) - cIn < 0;
1591
+ f.pv = ((a ^ b) & (a ^ r) & 32768) !== 0;
1592
+ f.n = true;
1593
+ f.c = diff < 0;
1594
+ f.y = (r & 8192) !== 0;
1595
+ f.x = (r & 2048) !== 0;
1596
+ return r;
1597
+ }
1598
+
1599
+ // src/cpu/z80/instructions/alu8.ts
1600
+ function applyAlu(cpu, kind, v) {
1601
+ const f = cpu.flags;
1602
+ const regs = cpu.regs;
1603
+ switch (kind) {
1604
+ case 0:
1605
+ regs.a = add8(f, regs.a, v, 0);
1606
+ break;
1607
+ // ADD
1608
+ case 1:
1609
+ regs.a = add8(f, regs.a, v, f.c ? 1 : 0);
1610
+ break;
1611
+ // ADC
1612
+ case 2:
1613
+ regs.a = sub8(f, regs.a, v, 0);
1614
+ break;
1615
+ // SUB
1616
+ case 3:
1617
+ regs.a = sub8(f, regs.a, v, f.c ? 1 : 0);
1618
+ break;
1619
+ // SBC
1620
+ case 4:
1621
+ regs.a = regs.a & v;
1622
+ setLogicFlags2(f, regs.a, true);
1623
+ break;
1624
+ // AND
1625
+ case 5:
1626
+ regs.a = regs.a ^ v;
1627
+ setLogicFlags2(f, regs.a, false);
1628
+ break;
1629
+ // XOR
1630
+ case 6:
1631
+ regs.a = regs.a | v;
1632
+ setLogicFlags2(f, regs.a, false);
1633
+ break;
1634
+ // OR
1635
+ case 7:
1636
+ cp8(f, regs.a, v);
1637
+ break;
1638
+ }
1639
+ }
1640
+ function registerAlu8(table, view) {
1641
+ for (let op = 128; op <= 191; op++) {
1642
+ const kind = op >> 3 & 7;
1643
+ const reg = op & 7;
1644
+ if (reg === 6) {
1645
+ table[op] = (cpu) => {
1646
+ const addr = view.memAddr(cpu);
1647
+ applyAlu(cpu, kind, cpu.bus.read(addr));
1648
+ return 7 + view.memExtra;
1649
+ };
1650
+ } else {
1651
+ table[op] = (cpu) => {
1652
+ applyAlu(cpu, kind, getR(cpu.regs, view, reg));
1653
+ return 4;
1654
+ };
1655
+ }
1656
+ }
1657
+ for (const op of [198, 206, 214, 222, 230, 238, 246, 254]) {
1658
+ const kind = op >> 3 & 7;
1659
+ table[op] = (cpu) => {
1660
+ applyAlu(cpu, kind, cpu.fetchByte());
1661
+ return 7;
1662
+ };
1663
+ }
1664
+ for (let r = 0; r < 8; r++) {
1665
+ const op = 4 | r << 3;
1666
+ if (r === 6) {
1667
+ table[op] = (cpu) => {
1668
+ const addr = view.memAddr(cpu);
1669
+ cpu.bus.write(addr, inc8(cpu.flags, cpu.bus.read(addr)));
1670
+ return 11 + view.memExtra;
1671
+ };
1672
+ } else {
1673
+ table[op] = (cpu) => {
1674
+ setR(cpu.regs, view, r, inc8(cpu.flags, getR(cpu.regs, view, r)));
1675
+ return 4;
1676
+ };
1677
+ }
1678
+ }
1679
+ for (let r = 0; r < 8; r++) {
1680
+ const op = 5 | r << 3;
1681
+ if (r === 6) {
1682
+ table[op] = (cpu) => {
1683
+ const addr = view.memAddr(cpu);
1684
+ cpu.bus.write(addr, dec8(cpu.flags, cpu.bus.read(addr)));
1685
+ return 11 + view.memExtra;
1686
+ };
1687
+ } else {
1688
+ table[op] = (cpu) => {
1689
+ setR(cpu.regs, view, r, dec8(cpu.flags, getR(cpu.regs, view, r)));
1690
+ return 4;
1691
+ };
1692
+ }
1693
+ }
1694
+ }
1695
+
1696
+ // src/cpu/z80/instructions/alu16.ts
1697
+ function registerAlu16(table, view) {
1698
+ table[3] = (cpu) => {
1699
+ cpu.regs.bc = u16(cpu.regs.bc + 1);
1700
+ return 6;
1701
+ };
1702
+ table[19] = (cpu) => {
1703
+ cpu.regs.de = u16(cpu.regs.de + 1);
1704
+ return 6;
1705
+ };
1706
+ table[35] = (cpu) => {
1707
+ view.setPair(cpu.regs, u16(view.getPair(cpu.regs) + 1));
1708
+ return 6;
1709
+ };
1710
+ table[51] = (cpu) => {
1711
+ cpu.regs.sp = u16(cpu.regs.sp + 1);
1712
+ return 6;
1713
+ };
1714
+ table[11] = (cpu) => {
1715
+ cpu.regs.bc = u16(cpu.regs.bc - 1);
1716
+ return 6;
1717
+ };
1718
+ table[27] = (cpu) => {
1719
+ cpu.regs.de = u16(cpu.regs.de - 1);
1720
+ return 6;
1721
+ };
1722
+ table[43] = (cpu) => {
1723
+ view.setPair(cpu.regs, u16(view.getPair(cpu.regs) - 1));
1724
+ return 6;
1725
+ };
1726
+ table[59] = (cpu) => {
1727
+ cpu.regs.sp = u16(cpu.regs.sp - 1);
1728
+ return 6;
1729
+ };
1730
+ const addHl = (getOperand) => (cpu) => {
1731
+ const hl = view.getPair(cpu.regs);
1732
+ cpu.regs.wz = u16(hl + 1);
1733
+ view.setPair(cpu.regs, add16(cpu.flags, hl, getOperand(cpu)));
1734
+ return 11;
1735
+ };
1736
+ table[9] = addHl((cpu) => cpu.regs.bc);
1737
+ table[25] = addHl((cpu) => cpu.regs.de);
1738
+ table[41] = addHl((cpu) => view.getPair(cpu.regs));
1739
+ table[57] = addHl((cpu) => cpu.regs.sp);
1740
+ }
1741
+
1742
+ // src/cpu/z80/instructions/rotate.ts
1743
+ function registerRotate2(table, _view) {
1744
+ const finish = (f, a, carry) => {
1745
+ f.c = carry !== 0;
1746
+ f.h = false;
1747
+ f.n = false;
1748
+ f.y = (a & 32) !== 0;
1749
+ f.x = (a & 8) !== 0;
1750
+ };
1751
+ table[7] = (cpu) => {
1752
+ const a = cpu.regs.a;
1753
+ const c = a >> 7 & 1;
1754
+ cpu.regs.a = (a << 1 | c) & 255;
1755
+ finish(cpu.flags, cpu.regs.a, c);
1756
+ return 4;
1757
+ };
1758
+ table[15] = (cpu) => {
1759
+ const a = cpu.regs.a;
1760
+ const c = a & 1;
1761
+ cpu.regs.a = (a >> 1 | c << 7) & 255;
1762
+ finish(cpu.flags, cpu.regs.a, c);
1763
+ return 4;
1764
+ };
1765
+ table[23] = (cpu) => {
1766
+ const a = cpu.regs.a;
1767
+ const c = a >> 7 & 1;
1768
+ cpu.regs.a = (a << 1 | (cpu.flags.c ? 1 : 0)) & 255;
1769
+ finish(cpu.flags, cpu.regs.a, c);
1770
+ return 4;
1771
+ };
1772
+ table[31] = (cpu) => {
1773
+ const a = cpu.regs.a;
1774
+ const c = a & 1;
1775
+ cpu.regs.a = (a >> 1 | (cpu.flags.c ? 128 : 0)) & 255;
1776
+ finish(cpu.flags, cpu.regs.a, c);
1777
+ return 4;
1778
+ };
1779
+ }
1780
+
1781
+ // src/cpu/z80/instructions/jump.ts
1782
+ function cond(f, cc) {
1783
+ switch (cc) {
1784
+ case 0:
1785
+ return !f.z;
1786
+ case 1:
1787
+ return f.z;
1788
+ case 2:
1789
+ return !f.c;
1790
+ case 3:
1791
+ return f.c;
1792
+ case 4:
1793
+ return !f.pv;
1794
+ case 5:
1795
+ return f.pv;
1796
+ case 6:
1797
+ return !f.s;
1798
+ default:
1799
+ return f.s;
1800
+ }
1801
+ }
1802
+ function registerJump(table, view) {
1803
+ table[195] = (cpu) => {
1804
+ const nn = cpu.fetchWord();
1805
+ cpu.regs.pc = nn;
1806
+ cpu.regs.wz = nn;
1807
+ return 10;
1808
+ };
1809
+ for (let cc = 0; cc < 8; cc++) {
1810
+ const op = 194 | cc << 3;
1811
+ table[op] = (cpu) => {
1812
+ const nn = cpu.fetchWord();
1813
+ cpu.regs.wz = nn;
1814
+ if (cond(cpu.flags, cc)) cpu.regs.pc = nn;
1815
+ return 10;
1816
+ };
1817
+ }
1818
+ table[24] = (cpu) => {
1819
+ const e = sext8(cpu.fetchByte());
1820
+ cpu.regs.pc = u16(cpu.regs.pc + e);
1821
+ cpu.regs.wz = cpu.regs.pc;
1822
+ return 12;
1823
+ };
1824
+ for (let cc = 0; cc < 4; cc++) {
1825
+ const op = 32 | cc << 3;
1826
+ table[op] = (cpu) => {
1827
+ const e = sext8(cpu.fetchByte());
1828
+ if (cond(cpu.flags, cc)) {
1829
+ cpu.regs.pc = u16(cpu.regs.pc + e);
1830
+ cpu.regs.wz = cpu.regs.pc;
1831
+ return 12;
1832
+ }
1833
+ return 7;
1834
+ };
1835
+ }
1836
+ table[16] = (cpu) => {
1837
+ const e = sext8(cpu.fetchByte());
1838
+ cpu.regs.b = cpu.regs.b - 1 & 255;
1839
+ if (cpu.regs.b !== 0) {
1840
+ cpu.regs.pc = u16(cpu.regs.pc + e);
1841
+ cpu.regs.wz = cpu.regs.pc;
1842
+ return 13;
1843
+ }
1844
+ return 8;
1845
+ };
1846
+ table[205] = (cpu) => {
1847
+ const nn = cpu.fetchWord();
1848
+ cpu.regs.wz = nn;
1849
+ cpu.push16(cpu.regs.pc);
1850
+ cpu.regs.pc = nn;
1851
+ return 17;
1852
+ };
1853
+ for (let cc = 0; cc < 8; cc++) {
1854
+ const op = 196 | cc << 3;
1855
+ table[op] = (cpu) => {
1856
+ const nn = cpu.fetchWord();
1857
+ cpu.regs.wz = nn;
1858
+ if (cond(cpu.flags, cc)) {
1859
+ cpu.push16(cpu.regs.pc);
1860
+ cpu.regs.pc = nn;
1861
+ return 17;
1862
+ }
1863
+ return 10;
1864
+ };
1865
+ }
1866
+ table[201] = (cpu) => {
1867
+ const pc = cpu.pop16();
1868
+ cpu.regs.pc = pc;
1869
+ cpu.regs.wz = pc;
1870
+ return 10;
1871
+ };
1872
+ for (let cc = 0; cc < 8; cc++) {
1873
+ const op = 192 | cc << 3;
1874
+ table[op] = (cpu) => {
1875
+ if (cond(cpu.flags, cc)) {
1876
+ const pc = cpu.pop16();
1877
+ cpu.regs.pc = pc;
1878
+ cpu.regs.wz = pc;
1879
+ return 11;
1880
+ }
1881
+ return 5;
1882
+ };
1883
+ }
1884
+ for (let n = 0; n < 8; n++) {
1885
+ const op = 199 | n << 3;
1886
+ const target = n << 3;
1887
+ table[op] = (cpu) => {
1888
+ cpu.push16(cpu.regs.pc);
1889
+ cpu.regs.pc = target;
1890
+ cpu.regs.wz = target;
1891
+ return 11;
1892
+ };
1893
+ }
1894
+ table[233] = (cpu) => {
1895
+ cpu.regs.pc = view.getPair(cpu.regs);
1896
+ return 4;
1897
+ };
1898
+ }
1899
+
1900
+ // src/cpu/z80/instructions/stack.ts
1901
+ function registerStack2(table, view) {
1902
+ table[197] = (cpu) => {
1903
+ cpu.push16(cpu.regs.bc);
1904
+ return 11;
1905
+ };
1906
+ table[213] = (cpu) => {
1907
+ cpu.push16(cpu.regs.de);
1908
+ return 11;
1909
+ };
1910
+ table[229] = (cpu) => {
1911
+ cpu.push16(view.getPair(cpu.regs));
1912
+ return 11;
1913
+ };
1914
+ table[245] = (cpu) => {
1915
+ cpu.push16(cpu.regs.a << 8 | cpu.flags.toByte());
1916
+ return 11;
1917
+ };
1918
+ table[193] = (cpu) => {
1919
+ cpu.regs.bc = cpu.pop16();
1920
+ return 10;
1921
+ };
1922
+ table[209] = (cpu) => {
1923
+ cpu.regs.de = cpu.pop16();
1924
+ return 10;
1925
+ };
1926
+ table[225] = (cpu) => {
1927
+ view.setPair(cpu.regs, cpu.pop16());
1928
+ return 10;
1929
+ };
1930
+ table[241] = (cpu) => {
1931
+ const v = cpu.pop16();
1932
+ cpu.regs.a = v >> 8 & 255;
1933
+ cpu.flags.fromByte(v & 255);
1934
+ return 10;
1935
+ };
1936
+ }
1937
+
1938
+ // src/cpu/z80/instructions/io.ts
1939
+ function registerIO2(table, _view) {
1940
+ table[219] = (cpu) => {
1941
+ const n = cpu.fetchByte();
1942
+ cpu.regs.wz = (cpu.regs.a << 8 | n) + 1 & 65535;
1943
+ cpu.regs.a = cpu.bus.ioRead(n) & 255;
1944
+ return 11;
1945
+ };
1946
+ table[211] = (cpu) => {
1947
+ const n = cpu.fetchByte();
1948
+ cpu.bus.ioWrite(n, cpu.regs.a);
1949
+ cpu.regs.wz = (cpu.regs.a << 8 | n + 1 & 255) & 65535;
1950
+ return 11;
1951
+ };
1952
+ }
1953
+
1954
+ // src/cpu/z80/instructions/control.ts
1955
+ function registerControl2(table, _view) {
1956
+ table[0] = (_cpu) => 4;
1957
+ table[118] = (cpu) => {
1958
+ cpu.halted = true;
1959
+ return 4;
1960
+ };
1961
+ table[243] = (cpu) => {
1962
+ cpu.iff1 = false;
1963
+ cpu.iff2 = false;
1964
+ cpu.pendingEI = false;
1965
+ return 4;
1966
+ };
1967
+ table[251] = (cpu) => {
1968
+ cpu.pendingEI = true;
1969
+ return 4;
1970
+ };
1971
+ table[39] = (cpu) => {
1972
+ const f = cpu.flags;
1973
+ const a0 = cpu.regs.a;
1974
+ let corr = 0;
1975
+ let carry = false;
1976
+ if (f.h || (a0 & 15) > 9) corr |= 6;
1977
+ if (f.c || a0 > 153) {
1978
+ corr |= 96;
1979
+ carry = true;
1980
+ }
1981
+ const a = (f.n ? a0 - corr : a0 + corr) & 255;
1982
+ cpu.regs.a = a;
1983
+ f.c = carry;
1984
+ f.h = ((a0 ^ a) & 16) !== 0;
1985
+ setSzyxp(f, a);
1986
+ return 4;
1987
+ };
1988
+ table[47] = (cpu) => {
1989
+ const f = cpu.flags;
1990
+ cpu.regs.a = ~cpu.regs.a & 255;
1991
+ f.h = true;
1992
+ f.n = true;
1993
+ f.y = (cpu.regs.a & 32) !== 0;
1994
+ f.x = (cpu.regs.a & 8) !== 0;
1995
+ return 4;
1996
+ };
1997
+ table[55] = (cpu) => {
1998
+ const f = cpu.flags;
1999
+ f.c = true;
2000
+ f.h = false;
2001
+ f.n = false;
2002
+ f.y = (cpu.regs.a & 32) !== 0;
2003
+ f.x = (cpu.regs.a & 8) !== 0;
2004
+ return 4;
2005
+ };
2006
+ table[63] = (cpu) => {
2007
+ const f = cpu.flags;
2008
+ f.h = f.c;
2009
+ f.c = !f.c;
2010
+ f.n = false;
2011
+ f.y = (cpu.regs.a & 32) !== 0;
2012
+ f.x = (cpu.regs.a & 8) !== 0;
2013
+ return 4;
2014
+ };
2015
+ }
2016
+
2017
+ // src/cpu/z80/instructions/bits.ts
2018
+ function rotShift(f, kind, v) {
2019
+ let c = 0;
2020
+ let r = 0;
2021
+ switch (kind) {
2022
+ case 0:
2023
+ c = v >> 7 & 1;
2024
+ r = (v << 1 | c) & 255;
2025
+ break;
2026
+ // RLC
2027
+ case 1:
2028
+ c = v & 1;
2029
+ r = (v >> 1 | c << 7) & 255;
2030
+ break;
2031
+ // RRC
2032
+ case 2:
2033
+ c = v >> 7 & 1;
2034
+ r = (v << 1 | (f.c ? 1 : 0)) & 255;
2035
+ break;
2036
+ // RL
2037
+ case 3:
2038
+ c = v & 1;
2039
+ r = (v >> 1 | (f.c ? 128 : 0)) & 255;
2040
+ break;
2041
+ // RR
2042
+ case 4:
2043
+ c = v >> 7 & 1;
2044
+ r = v << 1 & 255;
2045
+ break;
2046
+ // SLA
2047
+ case 5:
2048
+ c = v & 1;
2049
+ r = (v >> 1 | v & 128) & 255;
2050
+ break;
2051
+ // SRA
2052
+ case 6:
2053
+ c = v >> 7 & 1;
2054
+ r = (v << 1 | 1) & 255;
2055
+ break;
2056
+ // SLL (undocumented)
2057
+ case 7:
2058
+ c = v & 1;
2059
+ r = v >> 1 & 255;
2060
+ break;
2061
+ }
2062
+ setSzyxp(f, r);
2063
+ f.h = false;
2064
+ f.n = false;
2065
+ f.c = c === 1;
2066
+ return r;
2067
+ }
2068
+ function bitTest(f, b, v, xySource) {
2069
+ const bit = v & 1 << b;
2070
+ f.z = bit === 0;
2071
+ f.pv = f.z;
2072
+ f.s = b === 7 && bit !== 0;
2073
+ f.h = true;
2074
+ f.n = false;
2075
+ f.y = (xySource & 32) !== 0;
2076
+ f.x = (xySource & 8) !== 0;
2077
+ }
2078
+ function registerBits(cb, idxCb) {
2079
+ for (let op = 0; op < 64; op++) {
2080
+ const kind = op >> 3 & 7;
2081
+ const reg = op & 7;
2082
+ if (reg === 6) {
2083
+ cb[op] = (cpu) => {
2084
+ const addr = cpu.regs.hl;
2085
+ cpu.bus.write(addr, rotShift(cpu.flags, kind, cpu.bus.read(addr)));
2086
+ return 15;
2087
+ };
2088
+ } else {
2089
+ cb[op] = (cpu) => {
2090
+ setRealR(cpu.regs, reg, rotShift(cpu.flags, kind, getRealR(cpu.regs, reg)));
2091
+ return 8;
2092
+ };
2093
+ }
2094
+ }
2095
+ for (let op = 64; op <= 127; op++) {
2096
+ const b = op >> 3 & 7;
2097
+ const reg = op & 7;
2098
+ if (reg === 6) {
2099
+ cb[op] = (cpu) => {
2100
+ bitTest(cpu.flags, b, cpu.bus.read(cpu.regs.hl), cpu.regs.wz >> 8);
2101
+ return 12;
2102
+ };
2103
+ } else {
2104
+ cb[op] = (cpu) => {
2105
+ const v = getRealR(cpu.regs, reg);
2106
+ bitTest(cpu.flags, b, v, v);
2107
+ return 8;
2108
+ };
2109
+ }
2110
+ }
2111
+ for (let op = 128; op <= 255; op++) {
2112
+ const b = op >> 3 & 7;
2113
+ const reg = op & 7;
2114
+ const set = op >= 192;
2115
+ const mask = 1 << b;
2116
+ if (reg === 6) {
2117
+ cb[op] = (cpu) => {
2118
+ const addr = cpu.regs.hl;
2119
+ const v = cpu.bus.read(addr);
2120
+ cpu.bus.write(addr, set ? v | mask : v & ~mask);
2121
+ return 15;
2122
+ };
2123
+ } else {
2124
+ cb[op] = (cpu) => {
2125
+ const v = getRealR(cpu.regs, reg);
2126
+ setRealR(cpu.regs, reg, set ? v | mask : v & ~mask);
2127
+ return 8;
2128
+ };
2129
+ }
2130
+ }
2131
+ for (let op = 0; op < 64; op++) {
2132
+ const kind = op >> 3 & 7;
2133
+ const reg = op & 7;
2134
+ idxCb[op] = (cpu, addr) => {
2135
+ const r = rotShift(cpu.flags, kind, cpu.bus.read(addr));
2136
+ cpu.bus.write(addr, r);
2137
+ if (reg !== 6) setRealR(cpu.regs, reg, r);
2138
+ return 19;
2139
+ };
2140
+ }
2141
+ for (let op = 64; op <= 127; op++) {
2142
+ const b = op >> 3 & 7;
2143
+ idxCb[op] = (cpu, addr) => {
2144
+ bitTest(cpu.flags, b, cpu.bus.read(addr), addr >> 8);
2145
+ return 16;
2146
+ };
2147
+ }
2148
+ for (let op = 128; op <= 255; op++) {
2149
+ const b = op >> 3 & 7;
2150
+ const reg = op & 7;
2151
+ const set = op >= 192;
2152
+ const mask = 1 << b;
2153
+ idxCb[op] = (cpu, addr) => {
2154
+ const v = cpu.bus.read(addr);
2155
+ const r = set ? v | mask : v & ~mask;
2156
+ cpu.bus.write(addr, r);
2157
+ if (reg !== 6) setRealR(cpu.regs, reg, r);
2158
+ return 19;
2159
+ };
2160
+ }
2161
+ }
2162
+
2163
+ // src/cpu/z80/instructions/ed.ts
2164
+ function read162(cpu, addr) {
2165
+ return cpu.bus.read(addr) | cpu.bus.read(u16(addr + 1)) << 8;
2166
+ }
2167
+ function write162(cpu, addr, v) {
2168
+ cpu.bus.write(addr, v & 255);
2169
+ cpu.bus.write(u16(addr + 1), v >> 8 & 255);
2170
+ }
2171
+ function ldAToIR(cpu, v) {
2172
+ const f = cpu.flags;
2173
+ cpu.regs.a = v & 255;
2174
+ f.s = (v & 128) !== 0;
2175
+ f.z = (v & 255) === 0;
2176
+ f.y = (v & 32) !== 0;
2177
+ f.x = (v & 8) !== 0;
2178
+ f.h = false;
2179
+ f.n = false;
2180
+ f.pv = cpu.iff2;
2181
+ }
2182
+ function registerEd(ed) {
2183
+ const pairGet = {
2184
+ 0: (cpu) => cpu.regs.bc,
2185
+ 1: (cpu) => cpu.regs.de,
2186
+ 2: (cpu) => cpu.regs.hl,
2187
+ 3: (cpu) => cpu.regs.sp
2188
+ };
2189
+ const pairSet = {
2190
+ 0: (cpu, v) => {
2191
+ cpu.regs.bc = v;
2192
+ },
2193
+ 1: (cpu, v) => {
2194
+ cpu.regs.de = v;
2195
+ },
2196
+ 2: (cpu, v) => {
2197
+ cpu.regs.hl = v;
2198
+ },
2199
+ 3: (cpu, v) => {
2200
+ cpu.regs.sp = v;
2201
+ }
2202
+ };
2203
+ for (let p = 0; p < 4; p++) {
2204
+ ed[66 | p << 4] = (cpu) => {
2205
+ const hl = cpu.regs.hl;
2206
+ cpu.regs.wz = u16(hl + 1);
2207
+ cpu.regs.hl = sbc16(cpu.flags, hl, pairGet[p](cpu), cpu.flags.c ? 1 : 0);
2208
+ return 15;
2209
+ };
2210
+ ed[74 | p << 4] = (cpu) => {
2211
+ const hl = cpu.regs.hl;
2212
+ cpu.regs.wz = u16(hl + 1);
2213
+ cpu.regs.hl = adc16(cpu.flags, hl, pairGet[p](cpu), cpu.flags.c ? 1 : 0);
2214
+ return 15;
2215
+ };
2216
+ ed[67 | p << 4] = (cpu) => {
2217
+ const nn = cpu.fetchWord();
2218
+ write162(cpu, nn, pairGet[p](cpu));
2219
+ cpu.regs.wz = u16(nn + 1);
2220
+ return 20;
2221
+ };
2222
+ ed[75 | p << 4] = (cpu) => {
2223
+ const nn = cpu.fetchWord();
2224
+ pairSet[p](cpu, read162(cpu, nn));
2225
+ cpu.regs.wz = u16(nn + 1);
2226
+ return 20;
2227
+ };
2228
+ }
2229
+ const neg = (cpu) => {
2230
+ cpu.regs.a = sub8(cpu.flags, 0, cpu.regs.a, 0);
2231
+ return 8;
2232
+ };
2233
+ for (const op of [68, 76, 84, 92, 100, 108, 116, 124]) ed[op] = neg;
2234
+ const retn = (cpu) => {
2235
+ cpu.iff1 = cpu.iff2;
2236
+ const pc = cpu.pop16();
2237
+ cpu.regs.pc = pc;
2238
+ cpu.regs.wz = pc;
2239
+ return 14;
2240
+ };
2241
+ for (const op of [69, 85, 93, 101, 109, 117, 125]) ed[op] = retn;
2242
+ ed[77] = retn;
2243
+ const setIm = (mode) => (cpu) => {
2244
+ cpu.im = mode;
2245
+ return 8;
2246
+ };
2247
+ ed[70] = setIm(0);
2248
+ ed[78] = setIm(0);
2249
+ ed[102] = setIm(0);
2250
+ ed[110] = setIm(0);
2251
+ ed[86] = setIm(1);
2252
+ ed[118] = setIm(1);
2253
+ ed[94] = setIm(2);
2254
+ ed[126] = setIm(2);
2255
+ ed[71] = (cpu) => {
2256
+ cpu.regs.i = cpu.regs.a;
2257
+ return 9;
2258
+ };
2259
+ ed[79] = (cpu) => {
2260
+ cpu.regs.r = cpu.regs.a;
2261
+ return 9;
2262
+ };
2263
+ ed[87] = (cpu) => {
2264
+ ldAToIR(cpu, cpu.regs.i);
2265
+ return 9;
2266
+ };
2267
+ ed[95] = (cpu) => {
2268
+ ldAToIR(cpu, cpu.regs.r);
2269
+ return 9;
2270
+ };
2271
+ ed[103] = (cpu) => {
2272
+ const hl = cpu.regs.hl;
2273
+ const m = cpu.bus.read(hl);
2274
+ const a = cpu.regs.a;
2275
+ cpu.bus.write(hl, (a << 4 | m >> 4) & 255);
2276
+ cpu.regs.a = a & 240 | m & 15;
2277
+ setSzyxp(cpu.flags, cpu.regs.a);
2278
+ cpu.flags.h = false;
2279
+ cpu.flags.n = false;
2280
+ cpu.regs.wz = u16(hl + 1);
2281
+ return 18;
2282
+ };
2283
+ ed[111] = (cpu) => {
2284
+ const hl = cpu.regs.hl;
2285
+ const m = cpu.bus.read(hl);
2286
+ const a = cpu.regs.a;
2287
+ cpu.bus.write(hl, (m << 4 | a & 15) & 255);
2288
+ cpu.regs.a = a & 240 | m >> 4 & 15;
2289
+ setSzyxp(cpu.flags, cpu.regs.a);
2290
+ cpu.flags.h = false;
2291
+ cpu.flags.n = false;
2292
+ cpu.regs.wz = u16(hl + 1);
2293
+ return 18;
2294
+ };
2295
+ for (let r = 0; r < 8; r++) {
2296
+ ed[64 | r << 3] = (cpu) => {
2297
+ const v = cpu.bus.ioRead(cpu.regs.c) & 255;
2298
+ if (r !== 6) setRealR(cpu.regs, r, v);
2299
+ setSzyxp(cpu.flags, v);
2300
+ cpu.flags.h = false;
2301
+ cpu.flags.n = false;
2302
+ cpu.regs.wz = u16(cpu.regs.bc + 1);
2303
+ return 12;
2304
+ };
2305
+ }
2306
+ for (let r = 0; r < 8; r++) {
2307
+ ed[65 | r << 3] = (cpu) => {
2308
+ const v = r === 6 ? 0 : getRealR(cpu.regs, r);
2309
+ cpu.bus.ioWrite(cpu.regs.c, v);
2310
+ cpu.regs.wz = u16(cpu.regs.bc + 1);
2311
+ return 12;
2312
+ };
2313
+ }
2314
+ }
2315
+
2316
+ // src/cpu/z80/instructions/block.ts
2317
+ function registerBlock(ed) {
2318
+ const ld = (dir) => {
2319
+ const v = (cpu) => {
2320
+ const byte = cpu.bus.read(cpu.regs.hl);
2321
+ cpu.bus.write(cpu.regs.de, byte);
2322
+ cpu.regs.hl = u16(cpu.regs.hl + dir);
2323
+ cpu.regs.de = u16(cpu.regs.de + dir);
2324
+ cpu.regs.bc = u16(cpu.regs.bc - 1);
2325
+ const n = byte + cpu.regs.a & 255;
2326
+ const f = cpu.flags;
2327
+ f.h = false;
2328
+ f.n = false;
2329
+ f.pv = cpu.regs.bc !== 0;
2330
+ f.y = (n & 2) !== 0;
2331
+ f.x = (n & 8) !== 0;
2332
+ };
2333
+ ed[dir === 1 ? 160 : 168] = (cpu) => {
2334
+ v(cpu);
2335
+ return 16;
2336
+ };
2337
+ ed[dir === 1 ? 176 : 184] = (cpu) => {
2338
+ v(cpu);
2339
+ if (cpu.regs.bc !== 0) {
2340
+ cpu.regs.pc = u16(cpu.regs.pc - 2);
2341
+ cpu.regs.wz = u16(cpu.regs.pc + 1);
2342
+ return 21;
2343
+ }
2344
+ return 16;
2345
+ };
2346
+ };
2347
+ ld(1);
2348
+ ld(-1);
2349
+ const cp = (dir) => {
2350
+ const one = (cpu) => {
2351
+ const a = cpu.regs.a;
2352
+ const val = cpu.bus.read(cpu.regs.hl);
2353
+ const result = a - val & 255;
2354
+ cpu.regs.hl = u16(cpu.regs.hl + dir);
2355
+ cpu.regs.bc = u16(cpu.regs.bc - 1);
2356
+ cpu.regs.wz = u16(cpu.regs.wz + dir);
2357
+ const f = cpu.flags;
2358
+ f.n = true;
2359
+ f.h = (a & 15) - (val & 15) < 0;
2360
+ const n = result - (f.h ? 1 : 0) & 255;
2361
+ f.s = (result & 128) !== 0;
2362
+ f.z = result === 0;
2363
+ f.pv = cpu.regs.bc !== 0;
2364
+ f.y = (n & 2) !== 0;
2365
+ f.x = (n & 8) !== 0;
2366
+ };
2367
+ ed[dir === 1 ? 161 : 169] = (cpu) => {
2368
+ one(cpu);
2369
+ return 16;
2370
+ };
2371
+ ed[dir === 1 ? 177 : 185] = (cpu) => {
2372
+ one(cpu);
2373
+ if (cpu.regs.bc !== 0 && !cpu.flags.z) {
2374
+ cpu.regs.pc = u16(cpu.regs.pc - 2);
2375
+ cpu.regs.wz = u16(cpu.regs.pc + 1);
2376
+ return 21;
2377
+ }
2378
+ return 16;
2379
+ };
2380
+ };
2381
+ cp(1);
2382
+ cp(-1);
2383
+ const ini = (dir) => {
2384
+ const one = (cpu) => {
2385
+ const f = cpu.flags;
2386
+ cpu.regs.wz = u16(cpu.regs.bc + dir);
2387
+ const val = cpu.bus.ioRead(cpu.regs.c) & 255;
2388
+ cpu.bus.write(cpu.regs.hl, val);
2389
+ cpu.regs.hl = u16(cpu.regs.hl + dir);
2390
+ cpu.regs.b = cpu.regs.b - 1 & 255;
2391
+ const b = cpu.regs.b;
2392
+ f.n = (val & 128) !== 0;
2393
+ f.s = (b & 128) !== 0;
2394
+ f.z = b === 0;
2395
+ f.y = (b & 32) !== 0;
2396
+ f.x = (b & 8) !== 0;
2397
+ const k = val + (cpu.regs.c + dir & 255);
2398
+ f.h = k > 255;
2399
+ f.c = k > 255;
2400
+ f.pv = parityEven(k & 7 ^ b);
2401
+ };
2402
+ ed[dir === 1 ? 162 : 170] = (cpu) => {
2403
+ one(cpu);
2404
+ return 16;
2405
+ };
2406
+ ed[dir === 1 ? 178 : 186] = (cpu) => {
2407
+ one(cpu);
2408
+ if (cpu.regs.b !== 0) {
2409
+ cpu.regs.pc = u16(cpu.regs.pc - 2);
2410
+ return 21;
2411
+ }
2412
+ return 16;
2413
+ };
2414
+ };
2415
+ ini(1);
2416
+ ini(-1);
2417
+ const outi = (dir) => {
2418
+ const one = (cpu) => {
2419
+ const f = cpu.flags;
2420
+ const val = cpu.bus.read(cpu.regs.hl);
2421
+ cpu.regs.b = cpu.regs.b - 1 & 255;
2422
+ const b = cpu.regs.b;
2423
+ cpu.bus.ioWrite(cpu.regs.c, val);
2424
+ cpu.regs.hl = u16(cpu.regs.hl + dir);
2425
+ cpu.regs.wz = u16(cpu.regs.bc + dir);
2426
+ f.n = (val & 128) !== 0;
2427
+ f.s = (b & 128) !== 0;
2428
+ f.z = b === 0;
2429
+ f.y = (b & 32) !== 0;
2430
+ f.x = (b & 8) !== 0;
2431
+ const k = val + cpu.regs.l;
2432
+ f.h = k > 255;
2433
+ f.c = k > 255;
2434
+ f.pv = parityEven(k & 7 ^ b);
2435
+ };
2436
+ ed[dir === 1 ? 163 : 171] = (cpu) => {
2437
+ one(cpu);
2438
+ return 16;
2439
+ };
2440
+ ed[dir === 1 ? 179 : 187] = (cpu) => {
2441
+ one(cpu);
2442
+ if (cpu.regs.b !== 0) {
2443
+ cpu.regs.pc = u16(cpu.regs.pc - 2);
2444
+ return 21;
2445
+ }
2446
+ return 16;
2447
+ };
2448
+ };
2449
+ outi(1);
2450
+ outi(-1);
2451
+ }
2452
+
2453
+ // src/cpu/z80/DecoderZ80.ts
2454
+ function makeTable(name) {
2455
+ return new Array(256).fill((_cpu) => 4).map((_, i) => {
2456
+ return (_cpu) => {
2457
+ console.warn(`[Z80] Unimplemented ${name} opcode: 0x${i.toString(16).padStart(2, "0")}`);
2458
+ return 4;
2459
+ };
2460
+ });
2461
+ }
2462
+ function makeIdxCbTable() {
2463
+ return new Array(256).fill((_c, _a) => 8).map((_, i) => {
2464
+ return (_cpu, _addr) => {
2465
+ console.warn(`[Z80] Unimplemented DDCB opcode: 0x${i.toString(16).padStart(2, "0")}`);
2466
+ return 8;
2467
+ };
2468
+ });
2469
+ }
2470
+ var DecoderZ80 = class {
2471
+ main = makeTable("main");
2472
+ mainIX = makeTable("mainIX");
2473
+ mainIY = makeTable("mainIY");
2474
+ cb = makeTable("CB");
2475
+ ed = makeTable("ED");
2476
+ idxCb = makeIdxCbTable();
2477
+ constructor() {
2478
+ for (let i = 0; i < 256; i++) {
2479
+ this.ed[i] = (_cpu) => 8;
2480
+ }
2481
+ this.buildMain(this.main, HL_VIEW);
2482
+ this.buildMain(this.mainIX, IX_VIEW);
2483
+ this.buildMain(this.mainIY, IY_VIEW);
2484
+ registerBits(this.cb, this.idxCb);
2485
+ registerEd(this.ed);
2486
+ registerBlock(this.ed);
2487
+ this.wireCbDispatch();
2488
+ this.wireEdDispatch();
2489
+ }
2490
+ buildMain(table, view) {
2491
+ registerLoad(table, view);
2492
+ registerExchange(table, view);
2493
+ registerAlu8(table, view);
2494
+ registerAlu16(table, view);
2495
+ registerRotate2(table, view);
2496
+ registerJump(table, view);
2497
+ registerStack2(table, view);
2498
+ registerIO2(table, view);
2499
+ registerControl2(table, view);
2500
+ }
2501
+ /**
2502
+ * main[0xCB]: plain CB dispatch (fetch op, R++, run cb table).
2503
+ * mainIX/IY[0xCB]: DDCB/FDCB dispatch — displacement fetched BEFORE the final
2504
+ * opcode, and that final byte is NOT an M1 fetch (only DD and CB tick R).
2505
+ */
2506
+ wireCbDispatch() {
2507
+ this.main[203] = (cpu) => {
2508
+ const op = cpu.fetchByte();
2509
+ cpu.regs.incR();
2510
+ return this.cb[op](cpu);
2511
+ };
2512
+ const idxDispatch = (getPair) => (cpu) => {
2513
+ const d = sext8(cpu.fetchByte());
2514
+ cpu.regs.incR();
2515
+ const addr = getPair(cpu) + d & 65535;
2516
+ cpu.regs.wz = addr;
2517
+ const op = cpu.fetchByte();
2518
+ return this.idxCb[op](cpu, addr);
2519
+ };
2520
+ this.mainIX[203] = idxDispatch((cpu) => cpu.regs.ix);
2521
+ this.mainIY[203] = idxDispatch((cpu) => cpu.regs.iy);
2522
+ }
2523
+ /** main/mainIX/mainIY[0xED]: identical ED dispatch (a DD/FD before ED is ignored). */
2524
+ wireEdDispatch() {
2525
+ const dispatch = (cpu) => {
2526
+ const op = cpu.fetchByte();
2527
+ cpu.regs.incR();
2528
+ return this.ed[op](cpu);
2529
+ };
2530
+ this.main[237] = dispatch;
2531
+ this.mainIX[237] = dispatch;
2532
+ this.mainIY[237] = dispatch;
2533
+ }
2534
+ };
2535
+
2536
+ // src/cpu/z80/CpuZ80.ts
2537
+ var CpuZ80 = class {
2538
+ regs = new RegistersZ80();
2539
+ flags = new FlagsZ80();
2540
+ bus;
2541
+ pic;
2542
+ dec;
2543
+ iff1 = false;
2544
+ iff2 = false;
2545
+ im = 0;
2546
+ pendingEI = false;
2547
+ halted = false;
2548
+ pendingNMI = false;
2549
+ /** Front-panel status byte of the last instruction (8080-style approximation). */
2550
+ lastStatus = FETCH_STATUS;
2551
+ statusTable = buildStatusTable();
2552
+ constructor(bus, pic) {
2553
+ this.bus = bus;
2554
+ this.pic = pic;
2555
+ this.dec = new DecoderZ80();
2556
+ }
2557
+ /** Program counter accessor (ICpu). */
2558
+ get pc() {
2559
+ return this.regs.pc;
2560
+ }
2561
+ set pc(v) {
2562
+ this.regs.pc = u16(v);
2563
+ }
2564
+ /** Uniform register/flags snapshot for introspection (ICpu). */
2565
+ state() {
2566
+ const r = this.regs;
2567
+ return { pc: r.pc, sp: r.sp, a: r.a, f: this.flags.toByte(), b: r.b, c: r.c, d: r.d, e: r.e, h: r.h, l: r.l, halted: this.halted, inte: this.iff1, intPending: this.pic.hasPendingInterrupt(), status: this.lastStatus };
2568
+ }
2569
+ /** Assert a non-maskable interrupt (edge-triggered latch; serviced next step). */
2570
+ triggerNMI() {
2571
+ this.pendingNMI = true;
2572
+ }
2573
+ // --- Z80Core memory/stack helpers ---
2574
+ fetchByte() {
2575
+ const b = this.bus.read(this.regs.pc);
2576
+ this.regs.pc = u16(this.regs.pc + 1);
2577
+ return b;
2578
+ }
2579
+ fetchWord() {
2580
+ const lo2 = this.fetchByte();
2581
+ const hi2 = this.fetchByte();
2582
+ return hi2 << 8 | lo2;
2583
+ }
2584
+ push16(v) {
2585
+ this.regs.sp = u16(this.regs.sp - 1);
2586
+ this.bus.write(this.regs.sp, v >> 8 & 255);
2587
+ this.regs.sp = u16(this.regs.sp - 1);
2588
+ this.bus.write(this.regs.sp, v & 255);
2589
+ }
2590
+ pop16() {
2591
+ const lo2 = this.bus.read(this.regs.sp);
2592
+ this.regs.sp = u16(this.regs.sp + 1);
2593
+ const hi2 = this.bus.read(this.regs.sp);
2594
+ this.regs.sp = u16(this.regs.sp + 1);
2595
+ return hi2 << 8 | lo2;
2596
+ }
2597
+ reset() {
2598
+ this.regs.reset();
2599
+ this.flags.reset();
2600
+ this.iff1 = false;
2601
+ this.iff2 = false;
2602
+ this.im = 0;
2603
+ this.pendingEI = false;
2604
+ this.halted = false;
2605
+ this.pendingNMI = false;
2606
+ this.lastStatus = FETCH_STATUS;
2607
+ }
2608
+ step() {
2609
+ if (this.pendingNMI) return this.serviceNMI();
2610
+ const eiJustCommitted = this.pendingEI;
2611
+ if (this.pendingEI) {
2612
+ this.iff1 = true;
2613
+ this.iff2 = true;
2614
+ this.pendingEI = false;
2615
+ }
2616
+ if (this.iff1 && !eiJustCommitted && this.pic.hasPendingInterrupt()) {
2617
+ return this.serviceINT();
2618
+ }
2619
+ if (this.halted) {
2620
+ this.regs.incR();
2621
+ return 4;
2622
+ }
2623
+ let op = this.fetchByte();
2624
+ this.regs.incR();
2625
+ let table = this.dec.main;
2626
+ let prefixT = 0;
2627
+ while (op === 221 || op === 253) {
2628
+ table = op === 221 ? this.dec.mainIX : this.dec.mainIY;
2629
+ prefixT += 4;
2630
+ op = this.fetchByte();
2631
+ this.regs.incR();
2632
+ }
2633
+ this.lastStatus = this.statusTable[op];
2634
+ return prefixT + table[op](this);
2635
+ }
2636
+ serviceINT() {
2637
+ this.iff1 = false;
2638
+ this.iff2 = false;
2639
+ this.halted = false;
2640
+ this.lastStatus = INTA_STATUS;
2641
+ this.regs.incR();
2642
+ const ackByte = this.bus.acknowledgeInterrupt();
2643
+ switch (this.im) {
2644
+ case 0: {
2645
+ return 2 + this.dec.main[ackByte & 255](this);
2646
+ }
2647
+ case 1:
2648
+ this.push16(this.regs.pc);
2649
+ this.regs.pc = 56;
2650
+ this.regs.wz = 56;
2651
+ return 13;
2652
+ case 2: {
2653
+ const vector = (this.regs.i << 8 | ackByte & 255) & 65535;
2654
+ this.push16(this.regs.pc);
2655
+ const lo2 = this.bus.read(vector);
2656
+ const hi2 = this.bus.read(u16(vector + 1));
2657
+ this.regs.pc = hi2 << 8 | lo2;
2658
+ this.regs.wz = this.regs.pc;
2659
+ return 19;
2660
+ }
2661
+ }
2662
+ }
2663
+ serviceNMI() {
2664
+ this.pendingNMI = false;
2665
+ this.halted = false;
2666
+ this.iff1 = false;
2667
+ this.regs.incR();
2668
+ this.push16(this.regs.pc);
2669
+ this.regs.pc = 102;
2670
+ this.regs.wz = 102;
2671
+ return 11;
2672
+ }
2673
+ /** Run until halted or `maxCycles` T-states elapse; returns total T-states. */
2674
+ run(maxCycles = Infinity) {
2675
+ let total = 0n;
2676
+ while (!this.halted && total < BigInt(maxCycles)) {
2677
+ total += BigInt(this.step());
2678
+ }
2679
+ return total;
2680
+ }
2681
+ };
2682
+
2683
+ // src/bus/BusRegion.ts
2684
+ function createRegion(module) {
2685
+ return {
2686
+ start: module.baseAddress,
2687
+ end: module.baseAddress + module.size - 1,
2688
+ module
2689
+ };
2690
+ }
2691
+
2692
+ // src/io/IoSpace.ts
2693
+ var IoSpace = class {
2694
+ devices = /* @__PURE__ */ new Map();
2695
+ register(dev) {
2696
+ for (const port of dev.basePorts) {
2697
+ this.devices.set(port & 255, dev);
2698
+ }
2699
+ }
2700
+ read(port) {
2701
+ const dev = this.devices.get(port & 255);
2702
+ if (dev === void 0) return 255;
2703
+ return dev.ioRead(port & 255);
2704
+ }
2705
+ write(port, value) {
2706
+ const dev = this.devices.get(port & 255);
2707
+ if (dev === void 0) return;
2708
+ dev.ioWrite(port & 255, value & 255);
2709
+ }
2710
+ reset() {
2711
+ const seen = /* @__PURE__ */ new Set();
2712
+ for (const dev of this.devices.values()) {
2713
+ if (!seen.has(dev)) {
2714
+ seen.add(dev);
2715
+ dev.reset();
2716
+ }
2717
+ }
2718
+ }
2719
+ };
2720
+
2721
+ // src/bus/Bus.ts
2722
+ var Bus = class {
2723
+ regions = [];
2724
+ ioSpace;
2725
+ pic;
2726
+ constructor(pic) {
2727
+ this.pic = pic;
2728
+ this.ioSpace = new IoSpace();
2729
+ }
2730
+ attachMemory(mem) {
2731
+ this.regions.push(createRegion(mem));
2732
+ this.regions.sort((a, b) => a.start - b.start);
2733
+ }
2734
+ attachIODevice(dev) {
2735
+ this.ioSpace.register(dev);
2736
+ }
2737
+ read(address) {
2738
+ const addr = address & 65535;
2739
+ const region = this.findRegion(addr);
2740
+ if (region === void 0) return 255;
2741
+ return region.module.read(addr - region.start);
2742
+ }
2743
+ write(address, value) {
2744
+ const addr = address & 65535;
2745
+ const region = this.findRegion(addr);
2746
+ if (region === void 0) return;
2747
+ if (region.module.readOnly) return;
2748
+ region.module.write(addr - region.start, value);
2749
+ }
2750
+ ioRead(port) {
2751
+ return this.ioSpace.read(port & 255);
2752
+ }
2753
+ ioWrite(port, value) {
2754
+ this.ioSpace.write(port & 255, value & 255);
2755
+ }
2756
+ acknowledgeInterrupt() {
2757
+ return this.pic.acknowledge();
2758
+ }
2759
+ getPic() {
2760
+ return this.pic;
2761
+ }
2762
+ reset() {
2763
+ for (const region of this.regions) {
2764
+ region.module.reset();
2765
+ }
2766
+ this.ioSpace.reset();
2767
+ this.pic.reset();
2768
+ }
2769
+ findRegion(addr) {
2770
+ for (const region of this.regions) {
2771
+ if (addr >= region.start && addr <= region.end) {
2772
+ return region;
2773
+ }
2774
+ }
2775
+ return void 0;
2776
+ }
2777
+ };
2778
+
2779
+ // src/bus/SnoopBus.ts
2780
+ var SnoopBus = class {
2781
+ constructor(inner) {
2782
+ this.inner = inner;
2783
+ }
2784
+ observers = [];
2785
+ attach(observer) {
2786
+ this.observers.push(observer);
2787
+ }
2788
+ detach(observer) {
2789
+ const i = this.observers.indexOf(observer);
2790
+ if (i !== -1) this.observers.splice(i, 1);
2791
+ }
2792
+ read(address) {
2793
+ const value = this.inner.read(address);
2794
+ for (const obs of this.observers) obs.onMemRead?.(address & 65535, value);
2795
+ return value;
2796
+ }
2797
+ write(address, value) {
2798
+ this.inner.write(address, value);
2799
+ for (const obs of this.observers) obs.onMemWrite?.(address & 65535, value & 255);
2800
+ }
2801
+ ioRead(port) {
2802
+ const value = this.inner.ioRead(port);
2803
+ for (const obs of this.observers) obs.onIoRead?.(port & 255, value);
2804
+ return value;
2805
+ }
2806
+ ioWrite(port, value) {
2807
+ this.inner.ioWrite(port, value);
2808
+ for (const obs of this.observers) obs.onIoWrite?.(port & 255, value & 255);
2809
+ }
2810
+ acknowledgeInterrupt() {
2811
+ return this.inner.acknowledgeInterrupt();
2812
+ }
2813
+ };
2814
+
2815
+ // src/memory/Ram.ts
2816
+ var Ram = class {
2817
+ id;
2818
+ baseAddress;
2819
+ size;
2820
+ readOnly = false;
2821
+ data;
2822
+ constructor(id, baseAddress, size) {
2823
+ this.id = id;
2824
+ this.baseAddress = baseAddress & 65535;
2825
+ this.size = size;
2826
+ this.data = new Uint8Array(size);
2827
+ }
2828
+ read(offset) {
2829
+ if (offset < 0 || offset >= this.size) return 255;
2830
+ return this.data[offset] ?? 255;
2831
+ }
2832
+ write(offset, value) {
2833
+ if (offset < 0 || offset >= this.size) return;
2834
+ this.data[offset] = u8(value);
2835
+ }
2836
+ reset() {
2837
+ this.data.fill(0);
2838
+ }
2839
+ /** Load bytes into RAM starting at offset */
2840
+ load(data, offset = 0) {
2841
+ this.data.set(data, offset);
2842
+ }
2843
+ /** Direct access for testing */
2844
+ getBytes() {
2845
+ return this.data;
2846
+ }
2847
+ };
2848
+
2849
+ // src/memory/Rom.ts
2850
+ var Rom = class {
2851
+ id;
2852
+ baseAddress;
2853
+ size;
2854
+ readOnly = true;
2855
+ data;
2856
+ constructor(id, baseAddress, data) {
2857
+ this.id = id;
2858
+ this.baseAddress = baseAddress & 65535;
2859
+ this.size = data.length;
2860
+ this.data = new Uint8Array(data);
2861
+ }
2862
+ read(offset) {
2863
+ if (offset < 0 || offset >= this.size) return 255;
2864
+ return this.data[offset] ?? 255;
2865
+ }
2866
+ write(_offset, _value) {
2867
+ }
2868
+ reset() {
2869
+ }
2870
+ };
2871
+
2872
+ // src/memory/MemoryMappedIOAdapter.ts
2873
+ var MemoryMappedIOAdapter = class {
2874
+ id;
2875
+ baseAddress;
2876
+ size;
2877
+ readOnly = false;
2878
+ device;
2879
+ constructor(baseAddress, size, device) {
2880
+ this.id = `mmio:${device.id}`;
2881
+ this.baseAddress = baseAddress & 65535;
2882
+ this.size = size;
2883
+ this.device = device;
2884
+ }
2885
+ read(offset) {
2886
+ return this.device.ioRead(offset);
2887
+ }
2888
+ write(offset, value) {
2889
+ this.device.ioWrite(offset, value);
2890
+ }
2891
+ reset() {
2892
+ this.device.reset();
2893
+ }
2894
+ };
2895
+
2896
+ // src/interrupt/InterruptController.ts
2897
+ var InterruptController = class {
2898
+ id = "pic";
2899
+ pending = 0;
2900
+ // bitmask of pending IRQ lines
2901
+ lines;
2902
+ constructor(lines = 8) {
2903
+ this.lines = lines;
2904
+ }
2905
+ hasPendingInterrupt() {
2906
+ return this.pending !== 0;
2907
+ }
2908
+ acknowledge() {
2909
+ for (let i = 0; i < this.lines; i++) {
2910
+ if ((this.pending & 1 << i) !== 0) {
2911
+ this.pending &= ~(1 << i);
2912
+ return 199 | i << 3;
2913
+ }
2914
+ }
2915
+ return 255;
2916
+ }
2917
+ assertIRQ(line) {
2918
+ if (line >= 0 && line < this.lines) {
2919
+ this.pending |= 1 << line;
2920
+ }
2921
+ }
2922
+ clearIRQ(line) {
2923
+ if (line >= 0 && line < this.lines) {
2924
+ this.pending &= ~(1 << line);
2925
+ }
2926
+ }
2927
+ reset() {
2928
+ this.pending = 0;
2929
+ }
2930
+ };
2931
+
2932
+ // src/clock/ImmediateClock.ts
2933
+ var ImmediateClock = class {
2934
+ elapsed = 0n;
2935
+ addCycles(cycles) {
2936
+ this.elapsed += BigInt(cycles);
2937
+ }
2938
+ getElapsedCycles() {
2939
+ return this.elapsed;
2940
+ }
2941
+ reset() {
2942
+ this.elapsed = 0n;
2943
+ }
2944
+ };
2945
+
2946
+ // src/clock/SystemClock.ts
2947
+ var SystemClock = class {
2948
+ elapsed = 0n;
2949
+ hz;
2950
+ startTime;
2951
+ cyclesAtStart = 0n;
2952
+ now;
2953
+ constructor(hz = 2e6, now = () => performance.now()) {
2954
+ this.hz = hz;
2955
+ this.now = now;
2956
+ this.startTime = now();
2957
+ }
2958
+ addCycles(cycles) {
2959
+ this.elapsed += BigInt(cycles);
2960
+ }
2961
+ getElapsedCycles() {
2962
+ return this.elapsed;
2963
+ }
2964
+ reset() {
2965
+ this.elapsed = 0n;
2966
+ this.rebaseline();
2967
+ }
2968
+ get targetHz() {
2969
+ return this.hz;
2970
+ }
2971
+ /**
2972
+ * Change the target frequency. Re-baselines so the new rate applies from
2973
+ * this moment — cycles already executed are not retroactively repriced.
2974
+ */
2975
+ setHz(hz) {
2976
+ this.rebaseline();
2977
+ this.hz = hz;
2978
+ }
2979
+ /**
2980
+ * Forfeit any accumulated drift and restart pacing from "now". Used after
2981
+ * host stalls (GC pause, suspended tab) so the simulation doesn't sprint to
2982
+ * catch up on lost wall time.
2983
+ */
2984
+ resync() {
2985
+ this.rebaseline();
2986
+ }
2987
+ /**
2988
+ * Returns a promise that resolves after yielding to the event loop.
2989
+ * Callers should await this periodically to avoid blocking.
2990
+ */
2991
+ yield() {
2992
+ return new Promise((resolve) => setTimeout(resolve, 0));
2993
+ }
2994
+ /**
2995
+ * How far ahead (in ms) the simulation is relative to wall time.
2996
+ * Positive = running too fast (should sleep); negative = behind.
2997
+ */
2998
+ getAheadMs() {
2999
+ const wallMs = this.now() - this.startTime;
3000
+ const simMs = Number(this.elapsed - this.cyclesAtStart) / this.hz * 1e3;
3001
+ return simMs - wallMs;
3002
+ }
3003
+ rebaseline() {
3004
+ this.cyclesAtStart = this.elapsed;
3005
+ this.startTime = this.now();
3006
+ }
3007
+ };
3008
+
3009
+ // src/machine/MachineRunner.ts
3010
+ var MAX_SPEED_BATCH = 4e4;
3011
+ var MAX_SLICE_MS = 20;
3012
+ var RESYNC_BEHIND_MS = 250;
3013
+ var MachineRunner = class {
3014
+ constructor(cpu, options = {}) {
3015
+ this.cpu = cpu;
3016
+ this.hz = options.hz ?? 2e6;
3017
+ this.now = options.now ?? (() => performance.now());
3018
+ this.clock = new SystemClock(typeof this.hz === "number" ? this.hz : 2e6, this.now);
3019
+ this.schedule = options.schedule ?? ((fn, ms) => setTimeout(fn, ms));
3020
+ this.onError = options.onError ?? ((err) => {
3021
+ console.error("[MachineRunner]", err);
3022
+ });
3023
+ }
3024
+ hz;
3025
+ clock;
3026
+ schedule;
3027
+ onError;
3028
+ now;
3029
+ running = false;
3030
+ startedAt = 0;
3031
+ cyclesAtStart = 0n;
3032
+ get targetHz() {
3033
+ return this.hz;
3034
+ }
3035
+ /** Measured average speed in Hz since start() — for status displays. */
3036
+ get effectiveHz() {
3037
+ const wallSec = (this.now() - this.startedAt) / 1e3;
3038
+ if (!this.running || wallSec <= 0) return 0;
3039
+ return Number(this.clock.getElapsedCycles() - this.cyclesAtStart) / wallSec;
3040
+ }
3041
+ get isRunning() {
3042
+ return this.running;
3043
+ }
3044
+ /** Total simulated T-states executed across the runner's lifetime. */
3045
+ get elapsedCycles() {
3046
+ return this.clock.getElapsedCycles();
3047
+ }
3048
+ start() {
3049
+ if (this.running) return;
3050
+ this.running = true;
3051
+ this.startedAt = this.now();
3052
+ this.cyclesAtStart = this.clock.getElapsedCycles();
3053
+ this.clock.resync();
3054
+ this.schedule(this.tick, 0);
3055
+ }
3056
+ stop() {
3057
+ this.running = false;
3058
+ }
3059
+ /** Change speed while running; pacing restarts from this moment. */
3060
+ setHz(hz) {
3061
+ this.hz = hz;
3062
+ this.clock.setHz(typeof hz === "number" ? hz : this.clock.targetHz);
3063
+ }
3064
+ tick = () => {
3065
+ if (!this.running) return;
3066
+ try {
3067
+ if (this.hz === "max") {
3068
+ for (let i = 0; i < MAX_SPEED_BATCH; i++) this.clock.addCycles(this.cpu.step());
3069
+ this.schedule(this.tick, 0);
3070
+ return;
3071
+ }
3072
+ if (this.clock.getAheadMs() < -RESYNC_BEHIND_MS) this.clock.resync();
3073
+ const chunk = Math.max(1, Math.floor(this.hz / 1e3));
3074
+ const sliceStart = this.now();
3075
+ do {
3076
+ let cycles = 0;
3077
+ while (cycles < chunk) cycles += this.cpu.step();
3078
+ this.clock.addCycles(cycles);
3079
+ } while (this.clock.getAheadMs() < 0 && this.now() - sliceStart < MAX_SLICE_MS);
3080
+ const aheadMs = this.clock.getAheadMs();
3081
+ this.schedule(this.tick, aheadMs > 1 ? Math.floor(aheadMs) : 0);
3082
+ } catch (err) {
3083
+ this.running = false;
3084
+ this.onError(err);
3085
+ }
3086
+ };
3087
+ };
3088
+
3089
+ // src/machine/MachineSpec.ts
3090
+ var MachineSpecError = class extends Error {
3091
+ constructor(message) {
3092
+ super(message);
3093
+ this.name = "MachineSpecError";
3094
+ }
3095
+ };
3096
+
3097
+ // src/machine/buildMachine.ts
3098
+ var inU16 = (n) => Number.isInteger(n) && n >= 0 && n <= 65535;
3099
+ var regionEnd = (r) => r.base + r.size - 1;
3100
+ function buildMachine(spec, opts = {}) {
3101
+ validate(spec);
3102
+ const pic = new InterruptController();
3103
+ const bus = new Bus(pic);
3104
+ for (const region of spec.memory) {
3105
+ if (region.kind === "rom") {
3106
+ bus.attachMemory(new Rom(region.id, region.base, region.image));
3107
+ } else {
3108
+ const ram = new Ram(region.id, region.base, region.size);
3109
+ if (region.image) {
3110
+ for (let i = 0; i < region.image.length; i++) ram.write(i, region.image[i]);
3111
+ }
3112
+ bus.attachMemory(ram);
3113
+ }
3114
+ }
3115
+ const ctx = {
3116
+ pic,
3117
+ log: opts.log ?? (() => {
3118
+ }),
3119
+ services: opts.services ?? {}
3120
+ };
3121
+ const cards = [];
3122
+ for (const card of spec.cards) {
3123
+ const built = card.factory(card.id, card.config ?? {}, ctx);
3124
+ built.attach(bus);
3125
+ cards.push(built);
3126
+ }
3127
+ const cpu = spec.cpuKind === "z80" ? new CpuZ80(bus, pic) : new Cpu8080(bus, pic);
3128
+ cpu.reset();
3129
+ cpu.pc = spec.resetVector;
3130
+ const runner = new MachineRunner(cpu, { hz: spec.clock === "max" ? "max" : spec.clock.hz });
3131
+ return { cpu, bus, pic, cards, runner, spec };
3132
+ }
3133
+ function validate(spec) {
3134
+ if (spec.cpuKind !== "i8080" && spec.cpuKind !== "z80") {
3135
+ throw new MachineSpecError(`cpuKind must be "i8080" or "z80", got ${JSON.stringify(spec.cpuKind)}`);
3136
+ }
3137
+ if (spec.clock !== "max" && !(typeof spec.clock === "object" && spec.clock.hz > 0)) {
3138
+ throw new MachineSpecError(`clock must be "max" or { hz: >0 }, got ${JSON.stringify(spec.clock)}`);
3139
+ }
3140
+ if (!inU16(spec.resetVector)) {
3141
+ throw new MachineSpecError(`resetVector must be an integer in 0x0000..0xFFFF, got ${spec.resetVector}`);
3142
+ }
3143
+ for (const r of spec.memory) {
3144
+ if (r.kind === "mmio") {
3145
+ throw new MachineSpecError(
3146
+ `memory region "${r.id}": kind "mmio" is not supported by buildMachine (memory-mapped I/O is provided by cards)`
3147
+ );
3148
+ }
3149
+ if (!inU16(r.base) || !Number.isInteger(r.size) || r.size < 1 || r.base + r.size > 65536) {
3150
+ throw new MachineSpecError(
3151
+ `memory region "${r.id}": invalid base/size (base=${r.base}, size=${r.size}); must fit within 0x0000..0xFFFF`
3152
+ );
3153
+ }
3154
+ if (r.kind === "rom" && (!r.image || r.image.length !== r.size)) {
3155
+ throw new MachineSpecError(
3156
+ `memory region "${r.id}": rom requires an image whose length (${r.image?.length ?? 0}) equals size (${r.size})`
3157
+ );
3158
+ }
3159
+ if (r.kind === "ram" && r.image && r.image.length > r.size) {
3160
+ throw new MachineSpecError(
3161
+ `memory region "${r.id}": ram image length (${r.image.length}) exceeds size (${r.size})`
3162
+ );
3163
+ }
3164
+ }
3165
+ const sorted = [...spec.memory].sort((a, b) => a.base - b.base);
3166
+ for (let i = 0; i + 1 < sorted.length; i++) {
3167
+ const a = sorted[i];
3168
+ const b = sorted[i + 1];
3169
+ if (regionEnd(a) >= b.base) {
3170
+ throw new MachineSpecError(
3171
+ `memory regions overlap: "${a.id}" (0x${a.base.toString(16)}-0x${regionEnd(a).toString(16)}) and "${b.id}" (0x${b.base.toString(16)}-0x${regionEnd(b).toString(16)})`
3172
+ );
3173
+ }
3174
+ }
3175
+ const portOwner = /* @__PURE__ */ new Map();
3176
+ for (const card of spec.cards) {
3177
+ for (const port of card.claims?.ports ?? []) {
3178
+ const prev = portOwner.get(port & 255);
3179
+ if (prev !== void 0) {
3180
+ throw new MachineSpecError(
3181
+ `I/O port 0x${(port & 255).toString(16)} claimed by both "${prev}" and "${card.id}"`
3182
+ );
3183
+ }
3184
+ portOwner.set(port & 255, card.id);
3185
+ }
3186
+ }
3187
+ const irqOwner = /* @__PURE__ */ new Map();
3188
+ for (const card of spec.cards) {
3189
+ const irq = card.claims?.irq;
3190
+ if (irq === void 0 || irq === null) continue;
3191
+ const prev = irqOwner.get(irq);
3192
+ if (prev !== void 0) {
3193
+ throw new MachineSpecError(`IRQ line ${irq} claimed by both "${prev}" and "${card.id}"`);
3194
+ }
3195
+ irqOwner.set(irq, card.id);
3196
+ }
3197
+ }
3198
+
3199
+ // src/bundles/CardBundle.ts
3200
+ var CardConfigError = class extends Error {
3201
+ constructor(message) {
3202
+ super(message);
3203
+ this.name = "CardConfigError";
3204
+ }
3205
+ };
3206
+ function withDefaults(manifest, config = {}) {
3207
+ const out = {};
3208
+ for (const [key, spec] of Object.entries(manifest.configSchema)) {
3209
+ const value = key in config ? config[key] : spec.default;
3210
+ validateParam(manifest.name, key, spec, value);
3211
+ out[key] = value;
3212
+ }
3213
+ return out;
3214
+ }
3215
+ function validateParam(card, key, spec, value) {
3216
+ const where = `card "${card}" config "${key}"`;
3217
+ if (spec.type === "enum") {
3218
+ if (!spec.enum || !spec.enum.includes(value)) {
3219
+ throw new CardConfigError(`${where}: ${JSON.stringify(value)} is not one of ${JSON.stringify(spec.enum ?? [])}`);
3220
+ }
3221
+ return;
3222
+ }
3223
+ const hi2 = spec.max ?? (spec.type === "u16" ? 65535 : 255);
3224
+ const lo2 = spec.min ?? 0;
3225
+ if (typeof value !== "number" || !Number.isInteger(value) || value < lo2 || value > hi2) {
3226
+ throw new CardConfigError(`${where}: ${JSON.stringify(value)} must be an integer in ${lo2}..${hi2}`);
3227
+ }
3228
+ }
3229
+
3230
+ // src/cards/Usart8251.ts
3231
+ var Usart8251 = class {
3232
+ constructor(id, dataPort, ctrlPort) {
3233
+ this.dataPort = dataPort;
3234
+ this.ctrlPort = ctrlPort;
3235
+ this.id = id;
3236
+ this.basePorts = [dataPort, ctrlPort];
3237
+ }
3238
+ id;
3239
+ basePorts;
3240
+ phase = "mode";
3241
+ modeWord = 0;
3242
+ txEnable = false;
3243
+ rxEnable = false;
3244
+ // 3-bit error field: bit0=PE, bit1=OE, bit2=FE — packed into status bits 3-5
3245
+ errorFlags = 0;
3246
+ rxQueue = [];
3247
+ transmitCb;
3248
+ ctsActive = true;
3249
+ dsrActive = true;
3250
+ ioWrite(port, value) {
3251
+ if (port === this.ctrlPort) {
3252
+ if (this.phase === "mode") {
3253
+ this.modeWord = value;
3254
+ this.phase = "command";
3255
+ return;
3256
+ }
3257
+ if ((value & 64) !== 0) {
3258
+ this.phase = "mode";
3259
+ this.txEnable = false;
3260
+ this.rxEnable = false;
3261
+ this.errorFlags = 0;
3262
+ this.rxQueue = [];
3263
+ return;
3264
+ }
3265
+ this.txEnable = (value & 1) !== 0;
3266
+ this.rxEnable = (value & 4) !== 0;
3267
+ if ((value & 16) !== 0) {
3268
+ this.errorFlags = 0;
3269
+ }
3270
+ return;
3271
+ }
3272
+ if (port === this.dataPort) {
3273
+ if (this.txEnable && this.ctsActive) {
3274
+ this.transmitCb?.(u8(value));
3275
+ }
3276
+ }
3277
+ }
3278
+ ioRead(port) {
3279
+ if (port === this.ctrlPort) {
3280
+ return this.buildStatus();
3281
+ }
3282
+ const byte = this.rxQueue.shift();
3283
+ return byte !== void 0 ? byte : 255;
3284
+ }
3285
+ reset() {
3286
+ this.phase = "mode";
3287
+ this.modeWord = 0;
3288
+ this.txEnable = false;
3289
+ this.rxEnable = false;
3290
+ this.errorFlags = 0;
3291
+ this.rxQueue = [];
3292
+ }
3293
+ enqueueRx(byte) {
3294
+ this.rxQueue.push(u8(byte));
3295
+ }
3296
+ onTransmit(cb) {
3297
+ this.transmitCb = cb;
3298
+ }
3299
+ setCts(active) {
3300
+ this.ctsActive = active;
3301
+ }
3302
+ setDsr(active) {
3303
+ this.dsrActive = active;
3304
+ }
3305
+ buildStatus() {
3306
+ const txRDY = this.txEnable && this.ctsActive ? 1 : 0;
3307
+ const rxRDY = this.rxEnable && this.rxQueue.length > 0 ? 2 : 0;
3308
+ const txEMPTY = 4;
3309
+ const errors = (this.errorFlags & 7) << 3;
3310
+ const dsr = this.dsrActive ? 128 : 0;
3311
+ return txRDY | rxRDY | txEMPTY | errors | dsr;
3312
+ }
3313
+ };
3314
+
3315
+ // src/util/hostConsole.ts
3316
+ function writeHostStdout(text) {
3317
+ globalThis.process?.stdout?.write(text);
3318
+ }
3319
+
3320
+ // src/cards/ImsaiSioCard.ts
3321
+ var SioBoardCtrl = class {
3322
+ constructor(id, port, a, b) {
3323
+ this.a = a;
3324
+ this.b = b;
3325
+ this.id = id;
3326
+ this.basePorts = [port];
3327
+ }
3328
+ id;
3329
+ basePorts;
3330
+ ioRead(_port) {
3331
+ return 255;
3332
+ }
3333
+ ioWrite(_port, value) {
3334
+ if ((value & 3) !== 0) this.a.reset();
3335
+ if ((value & 12) !== 0) this.b.reset();
3336
+ }
3337
+ reset() {
3338
+ }
3339
+ };
3340
+ var ImsaiSioCard = class {
3341
+ id;
3342
+ channelA;
3343
+ channelB;
3344
+ boardCtrl;
3345
+ constructor(id = "sio2", options = {}) {
3346
+ const portA = options.basePortA ?? 2;
3347
+ const portB = options.basePortB ?? 4;
3348
+ const ctrlPort = options.boardCtrlPort ?? 8;
3349
+ this.id = id;
3350
+ this.channelA = new Usart8251(`${id}:a`, portA, portA + 1);
3351
+ this.channelB = new Usart8251(`${id}:b`, portB, portB + 1);
3352
+ this.boardCtrl = new SioBoardCtrl(`${id}:ctrl`, ctrlPort, this.channelA, this.channelB);
3353
+ }
3354
+ attach(bus) {
3355
+ bus.attachIODevice(this.channelA);
3356
+ bus.attachIODevice(this.channelB);
3357
+ bus.attachIODevice(this.boardCtrl);
3358
+ }
3359
+ wireToConsole() {
3360
+ const write = (byte) => writeHostStdout(String.fromCharCode(byte));
3361
+ this.channelA.onTransmit(write);
3362
+ this.channelB.onTransmit(write);
3363
+ }
3364
+ reset() {
3365
+ this.channelA.reset();
3366
+ this.channelB.reset();
3367
+ }
3368
+ };
3369
+
3370
+ // src/cards/Mc6850Acia.ts
3371
+ var Mc6850Acia = class {
3372
+ constructor(id, statusPort, dataPort) {
3373
+ this.statusPort = statusPort;
3374
+ this.dataPort = dataPort;
3375
+ this.id = id;
3376
+ this.basePorts = [statusPort, dataPort];
3377
+ }
3378
+ id;
3379
+ basePorts;
3380
+ controlReg = 0;
3381
+ rxQueue = [];
3382
+ // bit0=FE, bit1=OVRN, bit2=PE — positioned into status bits 4-6 when read.
3383
+ errorFlags = 0;
3384
+ rxIntEnable = false;
3385
+ txIntEnable = false;
3386
+ ctsClear = true;
3387
+ // CTS input grounded → clear to send
3388
+ carrierPresent = true;
3389
+ // DCD input grounded → carrier present
3390
+ transmitCb;
3391
+ ioRead(port) {
3392
+ if (port === this.statusPort) {
3393
+ return this.buildStatus();
3394
+ }
3395
+ const byte = this.rxQueue.shift();
3396
+ if (this.rxQueue.length === 0) {
3397
+ this.errorFlags &= ~2;
3398
+ }
3399
+ return byte !== void 0 ? byte : 255;
3400
+ }
3401
+ ioWrite(port, value) {
3402
+ if (port === this.statusPort) {
3403
+ this.writeControl(u8(value));
3404
+ return;
3405
+ }
3406
+ if (this.ctsClear) {
3407
+ this.transmitCb?.(u8(value));
3408
+ }
3409
+ }
3410
+ reset() {
3411
+ this.masterReset();
3412
+ this.controlReg = 0;
3413
+ }
3414
+ /**
3415
+ * Feed a received byte to the ACIA. Sets RDRF; a byte arriving while the
3416
+ * previous one has not been read flags an overrun (OVRN).
3417
+ */
3418
+ enqueueRx(byte) {
3419
+ if (this.rxQueue.length > 0) {
3420
+ this.errorFlags |= 2;
3421
+ }
3422
+ this.rxQueue.push(u8(byte));
3423
+ }
3424
+ onTransmit(cb) {
3425
+ this.transmitCb = cb;
3426
+ }
3427
+ /** Drive the CTS input: true = clear to send (grounded), false = inhibited. */
3428
+ setCts(clear) {
3429
+ this.ctsClear = clear;
3430
+ }
3431
+ /** Drive the DCD input: true = carrier present (grounded), false = lost. */
3432
+ setDcd(carrierPresent) {
3433
+ this.carrierPresent = carrierPresent;
3434
+ }
3435
+ /** Inject receive error flags (framing / overrun / parity) for the next read. */
3436
+ setErrors(fe, ovrn, pe) {
3437
+ this.errorFlags = (fe ? 1 : 0) | (ovrn ? 2 : 0) | (pe ? 4 : 0);
3438
+ }
3439
+ /** Last value written to the control register. */
3440
+ get control() {
3441
+ return this.controlReg;
3442
+ }
3443
+ writeControl(value) {
3444
+ this.controlReg = value;
3445
+ if ((value & 3) === 3) {
3446
+ this.masterReset();
3447
+ return;
3448
+ }
3449
+ this.rxIntEnable = (value & 128) !== 0;
3450
+ this.txIntEnable = (value & 96) === 32;
3451
+ }
3452
+ masterReset() {
3453
+ this.rxQueue = [];
3454
+ this.errorFlags = 0;
3455
+ this.rxIntEnable = false;
3456
+ this.txIntEnable = false;
3457
+ }
3458
+ buildStatus() {
3459
+ const rdrf = this.rxQueue.length > 0 && this.carrierPresent ? 1 : 0;
3460
+ const tdre = this.ctsClear ? 2 : 0;
3461
+ const dcd = this.carrierPresent ? 0 : 4;
3462
+ const cts = this.ctsClear ? 0 : 8;
3463
+ const errs = (this.errorFlags & 7) << 4;
3464
+ const irq = this.rxIntEnable && rdrf !== 0 || this.txIntEnable && tdre !== 0 ? 128 : 0;
3465
+ return rdrf | tdre | dcd | cts | errs | irq;
3466
+ }
3467
+ };
3468
+
3469
+ // src/cards/Mits2SioCard.ts
3470
+ var Mits2SioCard = class {
3471
+ id;
3472
+ port0;
3473
+ port1;
3474
+ constructor(id = "2sio", options = {}) {
3475
+ const base = options.basePort ?? 16;
3476
+ this.id = id;
3477
+ this.port0 = new Mc6850Acia(`${id}:port0`, base + 0, base + 1);
3478
+ this.port1 = new Mc6850Acia(`${id}:port1`, base + 2, base + 3);
3479
+ }
3480
+ attach(bus) {
3481
+ bus.attachIODevice(this.port0);
3482
+ bus.attachIODevice(this.port1);
3483
+ }
3484
+ wireToConsole() {
3485
+ this.port0.onTransmit((byte) => writeHostStdout(String.fromCharCode(byte & 127)));
3486
+ }
3487
+ reset() {
3488
+ this.port0.reset();
3489
+ this.port1.reset();
3490
+ }
3491
+ };
3492
+
3493
+ // src/cards/Tr1602Uart.ts
3494
+ var Tr1602Uart = class {
3495
+ constructor(id, dataPort, statusPort) {
3496
+ this.dataPort = dataPort;
3497
+ this.statusPort = statusPort;
3498
+ this.id = id;
3499
+ this.basePorts = [dataPort, statusPort];
3500
+ }
3501
+ id;
3502
+ basePorts;
3503
+ // bit0=PE, bit1=OE, bit2=FE — positioned into status bits 3-5 when read.
3504
+ errorFlags = 0;
3505
+ rxQueue = [];
3506
+ controlReg = 0;
3507
+ transmitCb;
3508
+ controlCb;
3509
+ ioRead(port) {
3510
+ if (port === this.statusPort) {
3511
+ return this.buildStatus();
3512
+ }
3513
+ const byte = this.rxQueue.shift();
3514
+ if (this.rxQueue.length === 0) {
3515
+ this.errorFlags &= ~2;
3516
+ }
3517
+ return byte !== void 0 ? byte : 255;
3518
+ }
3519
+ ioWrite(port, value) {
3520
+ if (port === this.statusPort) {
3521
+ this.controlReg = u8(value);
3522
+ this.controlCb?.(this.controlReg);
3523
+ return;
3524
+ }
3525
+ this.transmitCb?.(u8(value));
3526
+ }
3527
+ reset() {
3528
+ this.errorFlags = 0;
3529
+ this.rxQueue = [];
3530
+ this.controlReg = 0;
3531
+ }
3532
+ /**
3533
+ * Feed a received byte to the UART. Sets Data-Received (RxRDY). A byte
3534
+ * arriving while the single-byte buffer is still full flags an overrun (OE),
3535
+ * mirroring the TR1602's lack of a receive FIFO.
3536
+ */
3537
+ enqueueRx(byte) {
3538
+ if (this.rxQueue.length > 0) {
3539
+ this.errorFlags |= 2;
3540
+ }
3541
+ this.rxQueue.push(u8(byte));
3542
+ }
3543
+ onTransmit(cb) {
3544
+ this.transmitCb = cb;
3545
+ }
3546
+ /** Register a callback fired on writes to the board control register. */
3547
+ onControl(cb) {
3548
+ this.controlCb = cb;
3549
+ }
3550
+ /** Last value written to the board control register (statusPort write). */
3551
+ get control() {
3552
+ return this.controlReg;
3553
+ }
3554
+ /** Inject receive error flags (parity / overrun / framing) for the next read. */
3555
+ setErrors(pe, oe, fe) {
3556
+ this.errorFlags = (pe ? 1 : 0) | (oe ? 2 : 0) | (fe ? 4 : 0);
3557
+ }
3558
+ buildStatus() {
3559
+ const txRDY = 1;
3560
+ const rxRDY = this.rxQueue.length > 0 ? 2 : 0;
3561
+ const txEMPTY = 4;
3562
+ const errors = (this.errorFlags & 7) << 3;
3563
+ return txRDY | rxRDY | txEMPTY | errors;
3564
+ }
3565
+ };
3566
+
3567
+ // src/cards/Port8212.ts
3568
+ var Port8212 = class {
3569
+ id;
3570
+ basePorts;
3571
+ outputLatch = 0;
3572
+ inputLatch = 255;
3573
+ // unconnected TTL inputs float high
3574
+ writeCb;
3575
+ constructor(id, port) {
3576
+ this.id = id;
3577
+ this.basePorts = [port];
3578
+ }
3579
+ ioRead(_port) {
3580
+ return this.inputLatch;
3581
+ }
3582
+ ioWrite(_port, value) {
3583
+ this.outputLatch = u8(value);
3584
+ this.writeCb?.(this.outputLatch);
3585
+ }
3586
+ reset() {
3587
+ this.outputLatch = 0;
3588
+ this.inputLatch = 255;
3589
+ }
3590
+ /** Drive the port's input pins — the value the CPU reads from this port. */
3591
+ setInput(value) {
3592
+ this.inputLatch = u8(value);
3593
+ }
3594
+ /** The byte currently latched on the output pins. */
3595
+ get output() {
3596
+ return this.outputLatch;
3597
+ }
3598
+ /** Register a callback fired whenever the CPU writes the output latch. */
3599
+ onWrite(cb) {
3600
+ this.writeCb = cb;
3601
+ }
3602
+ };
3603
+
3604
+ // src/cards/ImsaiMioCard.ts
3605
+ var ImsaiMioCard = class {
3606
+ id;
3607
+ portA;
3608
+ portB;
3609
+ uart;
3610
+ constructor(id = "mio", options = {}) {
3611
+ const base = options.basePort ?? 16;
3612
+ this.id = id;
3613
+ this.portA = new Port8212(`${id}:portA`, base + 0);
3614
+ this.portB = new Port8212(`${id}:portB`, base + 1);
3615
+ this.uart = new Tr1602Uart(`${id}:uart`, base + 2, base + 3);
3616
+ }
3617
+ attach(bus) {
3618
+ bus.attachIODevice(this.portA);
3619
+ bus.attachIODevice(this.portB);
3620
+ bus.attachIODevice(this.uart);
3621
+ }
3622
+ wireToConsole() {
3623
+ this.uart.onTransmit((byte) => writeHostStdout(String.fromCharCode(byte & 127)));
3624
+ }
3625
+ reset() {
3626
+ this.portA.reset();
3627
+ this.portB.reset();
3628
+ this.uart.reset();
3629
+ }
3630
+ };
3631
+
3632
+ // src/cards/FdcPlusClient.ts
3633
+ var FdcPlusClient = class {
3634
+ constructor(ws) {
3635
+ this.ws = ws;
3636
+ ws.onmessage = (ev) => {
3637
+ const chunk = ev.data instanceof ArrayBuffer ? new Uint8Array(ev.data) : ev.data;
3638
+ this.rxBuf.push(chunk);
3639
+ this.rxTotal += chunk.length;
3640
+ this.drain();
3641
+ };
3642
+ ws.onerror = () => this.rejectAll(new Error("WebSocket error"));
3643
+ ws.onclose = () => this.rejectAll(new Error("WebSocket closed"));
3644
+ }
3645
+ rxBuf = [];
3646
+ rxTotal = 0;
3647
+ pending = [];
3648
+ // Wire mutex. The protocol is strictly request/response, and WRIT is a
3649
+ // multi-frame exchange (header → ack → raw data → status): any frame another
3650
+ // op sends mid-exchange is consumed by the server as track data, and the
3651
+ // tail of the real payload spills out as garbage commands, permanently
3652
+ // desyncing the stream. Null when the wire is idle.
3653
+ chainTail = null;
3654
+ stat(drive, headLoad, track) {
3655
+ return this.exchange(() => {
3656
+ const p1 = (headLoad ? 1 : 0) << 8 | drive & 255;
3657
+ this.ws.send(this.makeCmd("STAT", p1, track & 65535));
3658
+ return this.enqueue(8).then((buf) => this.parseCmd(buf).p2);
3659
+ });
3660
+ }
3661
+ readTrack(drive, track, length) {
3662
+ return this.exchange(() => {
3663
+ const p1 = (drive & 15) << 12 | track & 4095;
3664
+ this.ws.send(this.makeCmd("READ", p1, length));
3665
+ return this.enqueue(length);
3666
+ });
3667
+ }
3668
+ writeTrack(drive, track, data) {
3669
+ return this.exchange(() => {
3670
+ const p1 = (drive & 15) << 12 | track & 4095;
3671
+ this.ws.send(this.makeCmd("WRIT", p1, data.length));
3672
+ return this.enqueue(8).then((ackBuf) => {
3673
+ const ack = this.parseCmd(ackBuf);
3674
+ if (ack.p1 !== 0) throw new Error(`WRIT rejected: status ${ack.p1}`);
3675
+ this.ws.send(data);
3676
+ return this.enqueue(8).then((wstaBuf) => {
3677
+ const wsta = this.parseCmd(wstaBuf);
3678
+ if (wsta.p1 !== 0) throw new Error(`WSTA error: status ${wsta.p1}`);
3679
+ });
3680
+ });
3681
+ });
3682
+ }
3683
+ /**
3684
+ * Run one full request/response exchange with exclusive use of the wire.
3685
+ * Starts synchronously when the wire is idle (device registers depend on the
3686
+ * command going out on the same tick); queues behind the in-flight exchange
3687
+ * otherwise. A failed exchange never blocks the queue.
3688
+ */
3689
+ exchange(op) {
3690
+ const run = this.chainTail ? this.chainTail.then(op, op) : op();
3691
+ const tail = run.then(() => void 0, () => void 0);
3692
+ this.chainTail = tail;
3693
+ void tail.then(() => {
3694
+ if (this.chainTail === tail) this.chainTail = null;
3695
+ });
3696
+ return run;
3697
+ }
3698
+ makeCmd(mnemonic, p1, p2) {
3699
+ const buf = new Uint8Array(8);
3700
+ for (let i = 0; i < 4; i++) buf[i] = mnemonic.charCodeAt(i) & 255;
3701
+ buf[4] = p1 & 255;
3702
+ buf[5] = p1 >> 8 & 255;
3703
+ buf[6] = p2 & 255;
3704
+ buf[7] = p2 >> 8 & 255;
3705
+ return buf;
3706
+ }
3707
+ parseCmd(buf) {
3708
+ const cmd = String.fromCharCode(
3709
+ buf[0] ?? 0,
3710
+ buf[1] ?? 0,
3711
+ buf[2] ?? 0,
3712
+ buf[3] ?? 0
3713
+ );
3714
+ const p1 = (buf[4] ?? 0) | (buf[5] ?? 0) << 8;
3715
+ const p2 = (buf[6] ?? 0) | (buf[7] ?? 0) << 8;
3716
+ return { cmd, p1, p2 };
3717
+ }
3718
+ enqueue(bytesExpected) {
3719
+ return new Promise((resolve, reject) => {
3720
+ this.pending.push({ resolve, reject, bytesExpected });
3721
+ this.drain();
3722
+ });
3723
+ }
3724
+ drain() {
3725
+ while (this.pending.length > 0) {
3726
+ const head = this.pending[0];
3727
+ if (!head || this.rxTotal < head.bytesExpected) break;
3728
+ this.pending.shift();
3729
+ head.resolve(this.collect(head.bytesExpected));
3730
+ }
3731
+ }
3732
+ collect(n) {
3733
+ const out = new Uint8Array(n);
3734
+ let written = 0;
3735
+ while (written < n) {
3736
+ const chunk = this.rxBuf[0];
3737
+ if (!chunk) break;
3738
+ const needed = n - written;
3739
+ if (chunk.length <= needed) {
3740
+ out.set(chunk, written);
3741
+ written += chunk.length;
3742
+ this.rxBuf.shift();
3743
+ this.rxTotal -= chunk.length;
3744
+ } else {
3745
+ out.set(chunk.subarray(0, needed), written);
3746
+ this.rxBuf[0] = chunk.subarray(needed);
3747
+ this.rxTotal -= needed;
3748
+ written = n;
3749
+ }
3750
+ }
3751
+ return out;
3752
+ }
3753
+ rejectAll(err) {
3754
+ const entries = this.pending.splice(0);
3755
+ for (const e of entries) e.reject(err);
3756
+ }
3757
+ };
3758
+
3759
+ // src/cards/MitsDcddCard.ts
3760
+ var TRACK_LEN = 137 * 32;
3761
+ var BYTES_PER_SECTOR = 137;
3762
+ var MAX_TRACK = 76;
3763
+ var MitsDcddCard = class {
3764
+ id;
3765
+ basePorts;
3766
+ fdcClient;
3767
+ p1;
3768
+ p2;
3769
+ p3;
3770
+ selectedDrive = 255;
3771
+ // Head position is PER-DRIVE state: each drive's arm stays where it was
3772
+ // left. The BIOS seeks relatively from a per-drive track table in RAM, so a
3773
+ // single shared counter desyncs the moment two drives interleave seeks
3774
+ // (e.g. PIP copying between disks) and lands reads/writes on wrong tracks.
3775
+ driveTrack = new Array(16).fill(0);
3776
+ headLoaded = false;
3777
+ writeEnabled = false;
3778
+ fetchPending = false;
3779
+ trackData = null;
3780
+ writeBuffer = null;
3781
+ writtenSectors = /* @__PURE__ */ new Set();
3782
+ // sectors touched since Write Enable
3783
+ writeDirty = false;
3784
+ lastSector = -1;
3785
+ byteInSector = 0;
3786
+ cacheDrive = 255;
3787
+ // which drive the cached trackData belongs to
3788
+ /** Head position of the currently selected drive (0 when none selected). */
3789
+ get currentTrack() {
3790
+ return this.selectedDrive === 255 ? 0 : this.driveTrack[this.selectedDrive] ?? 0;
3791
+ }
3792
+ set currentTrack(track) {
3793
+ if (this.selectedDrive !== 255) this.driveTrack[this.selectedDrive] = track;
3794
+ }
3795
+ constructor(id, ws, options = {}) {
3796
+ this.id = id;
3797
+ const base = options.basePort ?? 8;
3798
+ this.p1 = base;
3799
+ this.p2 = base + 1;
3800
+ this.p3 = base + 2;
3801
+ this.basePorts = [this.p1, this.p2, this.p3];
3802
+ this.fdcClient = new FdcPlusClient(ws);
3803
+ }
3804
+ attach(bus) {
3805
+ bus.attachIODevice(this);
3806
+ }
3807
+ ioRead(port) {
3808
+ if (port === this.p1) return this.readStatus();
3809
+ if (port === this.p2) return this.readSector();
3810
+ if (port === this.p3) return this.readData();
3811
+ return 255;
3812
+ }
3813
+ ioWrite(port, value) {
3814
+ if (port === this.p1) {
3815
+ this.writeDriveSelect(value);
3816
+ return;
3817
+ }
3818
+ if (port === this.p2) {
3819
+ this.writeCommand(value);
3820
+ return;
3821
+ }
3822
+ if (port === this.p3) {
3823
+ this.writeData(value);
3824
+ return;
3825
+ }
3826
+ }
3827
+ reset() {
3828
+ this.flushWrite();
3829
+ this.selectedDrive = 255;
3830
+ this.driveTrack.fill(0);
3831
+ this.headLoaded = false;
3832
+ this.writeEnabled = false;
3833
+ this.fetchPending = false;
3834
+ this.trackData = null;
3835
+ this.writeBuffer = null;
3836
+ this.writtenSectors.clear();
3837
+ this.writeDirty = false;
3838
+ this.lastSector = -1;
3839
+ this.byteInSector = 0;
3840
+ this.cacheDrive = 255;
3841
+ }
3842
+ // --- Port 1: status (READ) / drive select (WRITE) ---
3843
+ readStatus() {
3844
+ this.ensureTrack();
3845
+ const nrda = !this.headLoaded || !this.trackData || this.fetchPending ? 128 : 0;
3846
+ const trk0 = this.currentTrack === 0 ? 0 : 64;
3847
+ const drvrdy = this.selectedDrive !== 255 ? 0 : 8;
3848
+ const hdstat = this.headLoaded ? 0 : 4;
3849
+ const mvhd = 0;
3850
+ const enwd = this.writeEnabled ? 0 : 1;
3851
+ return nrda | trk0 | drvrdy | hdstat | mvhd | enwd;
3852
+ }
3853
+ writeDriveSelect(value) {
3854
+ if ((value & 128) !== 0) {
3855
+ this.flushWrite();
3856
+ this.selectedDrive = 255;
3857
+ this.writeEnabled = false;
3858
+ return;
3859
+ }
3860
+ const drive = value & 15;
3861
+ if (drive !== this.selectedDrive) this.flushWrite();
3862
+ this.selectedDrive = drive;
3863
+ if (drive !== this.cacheDrive) {
3864
+ this.trackData = null;
3865
+ }
3866
+ this.ensureTrack();
3867
+ this.fdcClient.stat(this.selectedDrive, this.headLoaded, this.currentTrack).catch(() => {
3868
+ });
3869
+ }
3870
+ // --- Port 2: sector position (READ) / command (WRITE) ---
3871
+ readSector() {
3872
+ const revMs = 166.667;
3873
+ const secMs = revMs / 32;
3874
+ const pos = performance.now() % revMs;
3875
+ const sector = Math.floor(pos / secMs) & 31;
3876
+ const offset = pos % secMs;
3877
+ const sectorTrue = offset < 1 ? 0 : 1;
3878
+ if (sector !== this.lastSector || sectorTrue === 0) {
3879
+ this.lastSector = sector;
3880
+ this.byteInSector = 0;
3881
+ }
3882
+ return 192 | (sector & 31) << 1 | sectorTrue;
3883
+ }
3884
+ writeCommand(value) {
3885
+ if ((value & 128) !== 0) {
3886
+ this.writeEnabled = true;
3887
+ if (!this.writeBuffer) this.writeBuffer = new Uint8Array(TRACK_LEN);
3888
+ }
3889
+ if ((value & 8) !== 0) {
3890
+ this.flushWrite();
3891
+ this.headLoaded = false;
3892
+ this.trackData = null;
3893
+ }
3894
+ if ((value & 4) !== 0) {
3895
+ this.headLoaded = true;
3896
+ this.ensureTrack();
3897
+ }
3898
+ if ((value & 2) !== 0) this.issueStep("out");
3899
+ if ((value & 1) !== 0) this.issueStep("in");
3900
+ }
3901
+ // --- Port 3: data (READ/WRITE) ---
3902
+ readData() {
3903
+ if (!this.headLoaded || !this.trackData || this.fetchPending) return 255;
3904
+ if (this.byteInSector >= BYTES_PER_SECTOR) return 255;
3905
+ const sector = this.lastSector < 0 ? 0 : this.lastSector;
3906
+ const offset = sector * BYTES_PER_SECTOR + this.byteInSector;
3907
+ const source = this.writeBuffer && this.writtenSectors.has(sector) ? this.writeBuffer : this.trackData;
3908
+ if (offset >= source.length) return 255;
3909
+ const byte = source[offset] ?? 255;
3910
+ this.byteInSector++;
3911
+ return byte;
3912
+ }
3913
+ writeData(value) {
3914
+ if (!this.writeEnabled || !this.writeBuffer) return;
3915
+ if (this.byteInSector >= BYTES_PER_SECTOR) return;
3916
+ const sector = this.lastSector < 0 ? 0 : this.lastSector;
3917
+ const offset = sector * BYTES_PER_SECTOR + this.byteInSector;
3918
+ if (offset < this.writeBuffer.length) {
3919
+ this.writeBuffer[offset] = value & 255;
3920
+ this.writtenSectors.add(sector);
3921
+ this.writeDirty = true;
3922
+ }
3923
+ this.byteInSector++;
3924
+ }
3925
+ // --- Private helpers ---
3926
+ /**
3927
+ * Start fetching the current track if the head is loaded, a drive is
3928
+ * selected, we don't already have the data, and no fetch is in flight. Safe
3929
+ * to call from any read/command path — it converges on caching the track the
3930
+ * head is currently over.
3931
+ */
3932
+ ensureTrack() {
3933
+ if (!this.headLoaded || this.selectedDrive === 255) return;
3934
+ if (this.trackData || this.fetchPending) return;
3935
+ this.fetchTrack();
3936
+ }
3937
+ fetchTrack() {
3938
+ if (this.selectedDrive === 255 || !this.headLoaded || this.fetchPending) return;
3939
+ this.fetchPending = true;
3940
+ const drive = this.selectedDrive;
3941
+ const track = this.currentTrack;
3942
+ this.fdcClient.readTrack(drive, track, TRACK_LEN).then((data) => {
3943
+ if (track === this.currentTrack && drive === this.selectedDrive && !this.trackData) {
3944
+ this.trackData = data;
3945
+ this.cacheDrive = drive;
3946
+ }
3947
+ }).catch(() => {
3948
+ }).finally(() => {
3949
+ this.fetchPending = false;
3950
+ this.ensureTrack();
3951
+ });
3952
+ }
3953
+ flushWrite() {
3954
+ if (!this.writeDirty || !this.writeBuffer || this.selectedDrive === 255) return;
3955
+ const image = this.trackData ? new Uint8Array(this.trackData) : new Uint8Array(TRACK_LEN);
3956
+ for (const sector of this.writtenSectors) {
3957
+ const off = sector * BYTES_PER_SECTOR;
3958
+ image.set(this.writeBuffer.subarray(off, off + BYTES_PER_SECTOR), off);
3959
+ }
3960
+ const drive = this.selectedDrive;
3961
+ const track = this.currentTrack;
3962
+ this.writeBuffer = null;
3963
+ this.writtenSectors.clear();
3964
+ this.writeDirty = false;
3965
+ this.writeEnabled = false;
3966
+ this.trackData = image;
3967
+ this.cacheDrive = drive;
3968
+ this.fdcClient.writeTrack(drive, track, image).catch((e) => {
3969
+ console.error(`[${this.id}] track write failed (drive ${drive}, track ${track}): ${String(e)}`);
3970
+ });
3971
+ }
3972
+ issueStep(dir) {
3973
+ this.flushWrite();
3974
+ if (dir === "in") this.currentTrack = Math.min(MAX_TRACK, this.currentTrack + 1);
3975
+ else this.currentTrack = Math.max(0, this.currentTrack - 1);
3976
+ this.trackData = null;
3977
+ this.writeEnabled = false;
3978
+ this.ensureTrack();
3979
+ }
3980
+ };
3981
+
3982
+ // src/bundles/seed/index.ts
3983
+ var u82 = (v, fallback) => typeof v === "number" ? v & 255 : fallback;
3984
+ var u162 = (v, fallback) => typeof v === "number" ? v & 65535 : fallback;
3985
+ var deviceCard = (id, dev) => ({
3986
+ id,
3987
+ reset: () => dev.reset(),
3988
+ attach: (bus) => bus.attachIODevice(dev)
3989
+ });
3990
+ var memoryCard = (id) => ({
3991
+ id,
3992
+ reset: () => {
3993
+ },
3994
+ attach: () => {
3995
+ }
3996
+ });
3997
+ var cpuCard = (id) => ({
3998
+ id,
3999
+ reset: () => {
4000
+ },
4001
+ attach: () => {
4002
+ }
4003
+ });
4004
+ var mits2SioBundle = {
4005
+ manifest: {
4006
+ name: "mits-88-2sio",
4007
+ version: "1.0.0",
4008
+ type: "serial",
4009
+ kind: "card",
4010
+ maker: "MITS",
4011
+ summary: "MITS 88-2SIO dual serial interface (2\xD7 6850 ACIA).",
4012
+ configSchema: { basePort: { type: "u8", default: 16, min: 0, max: 252, description: "Base I/O port (claims base..base+3)" } }
4013
+ },
4014
+ cardFactory: (id, cfg) => new Mits2SioCard(id, { basePort: u82(cfg.basePort, 16) }),
4015
+ claims: (cfg) => {
4016
+ const b = u82(cfg.basePort, 16);
4017
+ return { ports: [b, b + 1, b + 2, b + 3] };
4018
+ }
4019
+ };
4020
+ var imsaiSioBundle = {
4021
+ manifest: {
4022
+ name: "imsai-sio2",
4023
+ version: "1.0.0",
4024
+ type: "serial",
4025
+ kind: "card",
4026
+ maker: "IMSAI",
4027
+ summary: "IMSAI SIO-2 dual 8251 serial card with a board-control port.",
4028
+ configSchema: {
4029
+ basePortA: { type: "u8", default: 2, min: 0, max: 254 },
4030
+ basePortB: { type: "u8", default: 4, min: 0, max: 254 },
4031
+ boardCtrlPort: { type: "u8", default: 8, min: 0, max: 255 }
4032
+ }
4033
+ },
4034
+ cardFactory: (id, cfg) => new ImsaiSioCard(id, {
4035
+ basePortA: u82(cfg.basePortA, 2),
4036
+ basePortB: u82(cfg.basePortB, 4),
4037
+ boardCtrlPort: u82(cfg.boardCtrlPort, 8)
4038
+ }),
4039
+ claims: (cfg) => {
4040
+ const a = u82(cfg.basePortA, 2);
4041
+ const b = u82(cfg.basePortB, 4);
4042
+ const c = u82(cfg.boardCtrlPort, 8);
4043
+ return { ports: [a, a + 1, b, b + 1, c] };
4044
+ }
4045
+ };
4046
+ var imsaiMioBundle = {
4047
+ manifest: {
4048
+ name: "imsai-mio",
4049
+ version: "1.0.0",
4050
+ type: "serial",
4051
+ kind: "card",
4052
+ maker: "IMSAI",
4053
+ summary: "IMSAI MIO multi-I/O card: two 8212 parallel ports (base+0/+1) and a TR1602 UART (base+2/+3).",
4054
+ configSchema: { basePort: { type: "u8", default: 16, min: 0, max: 252 } }
4055
+ },
4056
+ cardFactory: (id, cfg) => new ImsaiMioCard(id, { basePort: u82(cfg.basePort, 16) }),
4057
+ // Registers 4 consecutive ports: portA(base), portB(base+1), UART(base+2/+3).
4058
+ claims: (cfg) => {
4059
+ const b = u82(cfg.basePort, 16);
4060
+ return { ports: [b, b + 1, b + 2, b + 3] };
4061
+ }
4062
+ };
4063
+ var mitsDcddBundle = {
4064
+ manifest: {
4065
+ name: "mits-88-dcdd",
4066
+ version: "1.0.0",
4067
+ type: "floppy",
4068
+ kind: "card",
4069
+ maker: "MITS",
4070
+ summary: "MITS 88-DCDD 8-inch floppy controller; disk I/O over the FDC channel.",
4071
+ configSchema: { basePort: { type: "u8", default: 8, min: 0, max: 253 } }
4072
+ },
4073
+ cardFactory: (id, cfg, ctx) => {
4074
+ const ws = ctx.services.fdc;
4075
+ if (!ws) {
4076
+ throw new MachineSpecError(
4077
+ `card "${id}" (mits-88-dcdd) requires an FDC channel in ctx.services.fdc`
4078
+ );
4079
+ }
4080
+ return new MitsDcddCard(id, ws, { basePort: u82(cfg.basePort, 8) });
4081
+ },
4082
+ claims: (cfg) => {
4083
+ const b = u82(cfg.basePort, 8);
4084
+ return { ports: [b, b + 1, b + 2] };
4085
+ }
4086
+ };
4087
+ var usart8251Bundle = {
4088
+ manifest: {
4089
+ name: "intel-8251",
4090
+ version: "1.0.0",
4091
+ type: "serial",
4092
+ kind: "chip",
4093
+ maker: "Intel",
4094
+ summary: "Intel 8251 USART (data + control port).",
4095
+ configSchema: {
4096
+ dataPort: { type: "u8", default: 16, min: 0, max: 255 },
4097
+ ctrlPort: { type: "u8", default: 17, min: 0, max: 255 }
4098
+ }
4099
+ },
4100
+ cardFactory: (id, cfg) => deviceCard(id, new Usart8251(id, u82(cfg.dataPort, 16), u82(cfg.ctrlPort, 17))),
4101
+ claims: (cfg) => ({ ports: [u82(cfg.dataPort, 16), u82(cfg.ctrlPort, 17)] })
4102
+ };
4103
+ var mc6850Bundle = {
4104
+ manifest: {
4105
+ name: "motorola-6850",
4106
+ version: "1.0.0",
4107
+ type: "serial",
4108
+ kind: "chip",
4109
+ maker: "Motorola",
4110
+ summary: "Motorola 6850 ACIA (status + data port).",
4111
+ configSchema: {
4112
+ statusPort: { type: "u8", default: 16, min: 0, max: 255 },
4113
+ dataPort: { type: "u8", default: 17, min: 0, max: 255 }
4114
+ }
4115
+ },
4116
+ cardFactory: (id, cfg) => deviceCard(id, new Mc6850Acia(id, u82(cfg.statusPort, 16), u82(cfg.dataPort, 17))),
4117
+ claims: (cfg) => ({ ports: [u82(cfg.statusPort, 16), u82(cfg.dataPort, 17)] })
4118
+ };
4119
+ var port8212Bundle = {
4120
+ manifest: {
4121
+ name: "intel-8212",
4122
+ version: "1.0.0",
4123
+ type: "other",
4124
+ kind: "chip",
4125
+ maker: "Intel",
4126
+ summary: "Intel 8212 8-bit I/O port.",
4127
+ configSchema: { port: { type: "u8", default: 255, min: 0, max: 255 } }
4128
+ },
4129
+ cardFactory: (id, cfg) => deviceCard(id, new Port8212(id, u82(cfg.port, 255))),
4130
+ claims: (cfg) => ({ ports: [u82(cfg.port, 255)] })
4131
+ };
4132
+ var tr1602Bundle = {
4133
+ manifest: {
4134
+ name: "tr1602-uart",
4135
+ version: "1.0.0",
4136
+ type: "serial",
4137
+ kind: "chip",
4138
+ maker: "Western Digital",
4139
+ summary: "TR1602 UART (data + status port).",
4140
+ configSchema: {
4141
+ dataPort: { type: "u8", default: 0, min: 0, max: 255 },
4142
+ statusPort: { type: "u8", default: 1, min: 0, max: 255 }
4143
+ }
4144
+ },
4145
+ cardFactory: (id, cfg) => deviceCard(id, new Tr1602Uart(id, u82(cfg.dataPort, 0), u82(cfg.statusPort, 1))),
4146
+ claims: (cfg) => ({ ports: [u82(cfg.dataPort, 0), u82(cfg.statusPort, 1)] })
4147
+ };
4148
+ var ramCardBundle = {
4149
+ manifest: {
4150
+ name: "ram-card",
4151
+ version: "1.0.0",
4152
+ type: "memory",
4153
+ kind: "card",
4154
+ maker: "generic",
4155
+ summary: "Static RAM board \u2014 maps read/write RAM at a configurable base address.",
4156
+ configSchema: {
4157
+ base: { type: "u16", default: 0, min: 0, max: 65535, description: "Start address" },
4158
+ size: { type: "u16", default: 16384, min: 1, max: 65535, description: "Bytes of RAM" }
4159
+ }
4160
+ },
4161
+ cardFactory: (id) => memoryCard(id),
4162
+ claims: () => ({ ports: [] }),
4163
+ memory: (cfg) => [{ id: "ram", base: u162(cfg.base, 0), size: u162(cfg.size, 16384), kind: "ram" }]
4164
+ };
4165
+ var epromCardBundle = {
4166
+ manifest: {
4167
+ name: "eprom-card",
4168
+ version: "1.0.0",
4169
+ type: "memory",
4170
+ kind: "card",
4171
+ maker: "generic",
4172
+ summary: "EPROM board \u2014 maps a read-only region at a base address; burn a .bin/Intel-HEX image into it.",
4173
+ configSchema: {
4174
+ base: { type: "u16", default: 61440, min: 0, max: 65535, description: "Start address" },
4175
+ size: { type: "u16", default: 2048, min: 1, max: 65535, description: "EPROM size in bytes" }
4176
+ }
4177
+ },
4178
+ cardFactory: (id) => memoryCard(id),
4179
+ claims: () => ({ ports: [] }),
4180
+ memory: (cfg) => {
4181
+ const base = u162(cfg.base, 61440);
4182
+ const size = u162(cfg.size, 2048);
4183
+ return [{ id: "rom", base, size, kind: "rom", image: new Uint8Array(size) }];
4184
+ }
4185
+ };
4186
+ var i8080CpuBundle = {
4187
+ manifest: {
4188
+ name: "i8080-cpu",
4189
+ version: "1.0.0",
4190
+ type: "cpu",
4191
+ kind: "card",
4192
+ maker: "Intel",
4193
+ summary: "Intel 8080 CPU board \u2014 the bus master; sets the power-on jump address.",
4194
+ configSchema: {
4195
+ resetVector: { type: "u16", default: 0, min: 0, max: 65535, description: "Power-on jump (program counter at reset)" }
4196
+ }
4197
+ },
4198
+ cardFactory: (id) => cpuCard(id),
4199
+ claims: () => ({ ports: [] }),
4200
+ cpu: (cfg) => ({ kind: "i8080", resetVector: u162(cfg.resetVector, 0) })
4201
+ };
4202
+ var z80CpuBundle = {
4203
+ manifest: {
4204
+ name: "z80-cpu",
4205
+ version: "1.0.0",
4206
+ type: "cpu",
4207
+ kind: "card",
4208
+ maker: "Zilog",
4209
+ summary: "Zilog Z80 CPU board \u2014 the bus master; sets the power-on jump address.",
4210
+ configSchema: {
4211
+ resetVector: { type: "u16", default: 0, min: 0, max: 65535, description: "Power-on jump (program counter at reset)" }
4212
+ }
4213
+ },
4214
+ cardFactory: (id) => cpuCard(id),
4215
+ claims: () => ({ ports: [] }),
4216
+ cpu: (cfg) => ({ kind: "z80", resetVector: u162(cfg.resetVector, 0) })
4217
+ };
4218
+ var seedBundles = [
4219
+ i8080CpuBundle,
4220
+ z80CpuBundle,
4221
+ mits2SioBundle,
4222
+ imsaiSioBundle,
4223
+ imsaiMioBundle,
4224
+ mitsDcddBundle,
4225
+ usart8251Bundle,
4226
+ mc6850Bundle,
4227
+ port8212Bundle,
4228
+ tr1602Bundle,
4229
+ ramCardBundle,
4230
+ epromCardBundle
4231
+ ];
4232
+ var seedBundleByName = (name) => seedBundles.find((b) => b.manifest.name === name);
4233
+
4234
+ // src/cards/SerialCard.ts
4235
+ var SerialCard = class {
4236
+ id;
4237
+ channel;
4238
+ constructor(id = "serial", opts = {}) {
4239
+ const dataPort = opts.dataPort ?? 16;
4240
+ const ctrlPort = opts.ctrlPort ?? 17;
4241
+ this.id = id;
4242
+ this.channel = opts.chip === "m6850" ? new Mc6850Acia(`${id}:ch`, ctrlPort, dataPort) : new Usart8251(`${id}:ch`, dataPort, ctrlPort);
4243
+ }
4244
+ attach(bus) {
4245
+ bus.attachIODevice(this.channel);
4246
+ }
4247
+ reset() {
4248
+ this.channel.reset();
4249
+ }
4250
+ };
4251
+
4252
+ // src/cards/ParallelCard.ts
4253
+ var ParallelCard = class {
4254
+ id;
4255
+ direction;
4256
+ gpio;
4257
+ dev;
4258
+ constructor(id = "parallel", opts = {}) {
4259
+ this.id = id;
4260
+ this.direction = opts.direction ?? "out";
4261
+ this.dev = new Port8212(`${id}:port`, opts.port ?? 0);
4262
+ this.gpio = {
4263
+ direction: this.direction,
4264
+ read: () => this.dev.output,
4265
+ setInput: (byte) => this.dev.setInput(byte),
4266
+ onOutput: (cb) => this.dev.onWrite(cb)
4267
+ };
4268
+ }
4269
+ attach(bus) {
4270
+ bus.attachIODevice(this.dev);
4271
+ }
4272
+ reset() {
4273
+ this.dev.reset();
4274
+ }
4275
+ };
4276
+
4277
+ // src/cards/KeyboardCard.ts
4278
+ var KeyboardDevice = class {
4279
+ id;
4280
+ basePorts;
4281
+ dataPort;
4282
+ statusPort;
4283
+ readyMask;
4284
+ queue = [];
4285
+ constructor(id, dataPort, statusPort, readyMask) {
4286
+ this.id = id;
4287
+ this.dataPort = u8(dataPort);
4288
+ this.statusPort = u8(statusPort);
4289
+ this.readyMask = u8(readyMask);
4290
+ this.basePorts = this.dataPort === this.statusPort ? [this.dataPort] : [this.dataPort, this.statusPort];
4291
+ }
4292
+ ioRead(port) {
4293
+ const p = u8(port);
4294
+ if (p === this.dataPort) return this.queue.shift() ?? 0;
4295
+ if (p === this.statusPort) return this.queue.length > 0 ? this.readyMask : 0;
4296
+ return 0;
4297
+ }
4298
+ ioWrite() {
4299
+ }
4300
+ reset() {
4301
+ this.queue.length = 0;
4302
+ }
4303
+ press(byte) {
4304
+ this.queue.push(u8(byte));
4305
+ }
4306
+ get pending() {
4307
+ return this.queue.length;
4308
+ }
4309
+ };
4310
+ var KeyboardCard = class {
4311
+ id;
4312
+ keyboard;
4313
+ dev;
4314
+ constructor(id = "keyboard", opts = {}) {
4315
+ this.id = id;
4316
+ this.dev = new KeyboardDevice(
4317
+ `${id}:kbd`,
4318
+ opts.dataPort ?? 1,
4319
+ opts.statusPort ?? 0,
4320
+ opts.readyMask ?? 1
4321
+ );
4322
+ const dev = this.dev;
4323
+ this.keyboard = {
4324
+ press: (byte) => dev.press(byte),
4325
+ type: (text) => {
4326
+ for (let i = 0; i < text.length; i++) dev.press(text.charCodeAt(i));
4327
+ },
4328
+ get pending() {
4329
+ return dev.pending;
4330
+ }
4331
+ };
4332
+ }
4333
+ attach(bus) {
4334
+ bus.attachIODevice(this.dev);
4335
+ }
4336
+ reset() {
4337
+ this.dev.reset();
4338
+ }
4339
+ };
4340
+
4341
+ // src/cards/VdmCard.ts
4342
+ var COLS = 64;
4343
+ var ROWS = 16;
4344
+ var SIZE = COLS * ROWS;
4345
+ var VdmCard = class {
4346
+ id;
4347
+ display;
4348
+ base;
4349
+ bus;
4350
+ constructor(id = "vdm", opts = {}) {
4351
+ this.id = id;
4352
+ this.base = (opts.base ?? 52224) & 65535;
4353
+ this.display = {
4354
+ descriptor: { mode: "charGrid", cols: COLS, rows: ROWS, font: "vdm", attrBit: 7 },
4355
+ frame: () => ({ bytes: this.readVram(), state: {} })
4356
+ };
4357
+ }
4358
+ attach(bus) {
4359
+ this.bus = bus;
4360
+ }
4361
+ reset() {
4362
+ }
4363
+ readVram() {
4364
+ const out = new Uint8Array(SIZE);
4365
+ const bus = this.bus;
4366
+ if (bus) for (let i = 0; i < SIZE; i++) out[i] = bus.read(this.base + i & 65535) & 255;
4367
+ return out;
4368
+ }
4369
+ };
4370
+
4371
+ // src/cards/DazzlerCard.ts
4372
+ var MAX_BUFFER = 2048;
4373
+ var DazzlerCard = class {
4374
+ id;
4375
+ display;
4376
+ controlPort;
4377
+ formatPort;
4378
+ control = 0;
4379
+ // D7 = on, D0–D6 = page
4380
+ format = 0;
4381
+ // resolution / colour / size
4382
+ frameFlip = 0;
4383
+ // toggling sync bit for control-port reads
4384
+ bus;
4385
+ dev;
4386
+ constructor(id = "dazzler", opts = {}) {
4387
+ this.id = id;
4388
+ this.controlPort = (opts.controlPort ?? 14) & 255;
4389
+ this.formatPort = (opts.formatPort ?? 15) & 255;
4390
+ const self = this;
4391
+ this.dev = {
4392
+ id: `${id}:io`,
4393
+ basePorts: this.controlPort === this.formatPort ? [this.controlPort] : [this.controlPort, this.formatPort],
4394
+ ioRead(port) {
4395
+ if ((port & 255) === self.controlPort) {
4396
+ self.frameFlip ^= 64;
4397
+ return self.frameFlip;
4398
+ }
4399
+ return 0;
4400
+ },
4401
+ ioWrite(port, value) {
4402
+ if ((port & 255) === self.controlPort) self.control = value & 255;
4403
+ else if ((port & 255) === self.formatPort) self.format = value & 255;
4404
+ },
4405
+ reset() {
4406
+ self.control = 0;
4407
+ self.format = 0;
4408
+ self.frameFlip = 0;
4409
+ }
4410
+ };
4411
+ this.display = {
4412
+ descriptor: { mode: "bitmap", width: 128, height: 128, format: "dazzler" },
4413
+ frame: () => ({
4414
+ bytes: this.dma(),
4415
+ state: { on: this.control >> 7 & 1, format: this.format, control: this.control }
4416
+ })
4417
+ };
4418
+ }
4419
+ attach(bus) {
4420
+ this.bus = bus;
4421
+ bus.attachIODevice(this.dev);
4422
+ }
4423
+ reset() {
4424
+ this.dev.reset();
4425
+ }
4426
+ /** DMA the display buffer out of system RAM at the current page. */
4427
+ dma() {
4428
+ const out = new Uint8Array(MAX_BUFFER);
4429
+ const bus = this.bus;
4430
+ if (!bus) return out;
4431
+ const base = (this.control & 127) << 9;
4432
+ for (let i = 0; i < MAX_BUFFER; i++) out[i] = bus.read(base + i & 65535) & 255;
4433
+ return out;
4434
+ }
4435
+ };
4436
+
4437
+ // src/cards/RtcCard.ts
4438
+ var EPOCH_ZERO = () => /* @__PURE__ */ new Date(0);
4439
+ var bcd = (n) => (Math.floor(n / 10) % 10 << 4 | n % 10) & 255;
4440
+ var RtcCard = class {
4441
+ id;
4442
+ base;
4443
+ now;
4444
+ ram = new Uint8Array(32);
4445
+ // 0x08..0x1F are read/write storage
4446
+ dev;
4447
+ constructor(id = "rtc", opts = {}, clock = EPOCH_ZERO) {
4448
+ this.id = id;
4449
+ this.base = (opts.base ?? 64) & 255;
4450
+ this.now = clock;
4451
+ const self = this;
4452
+ const ports = [];
4453
+ for (let i = 0; i < 32; i++) ports.push(this.base + i & 255);
4454
+ this.dev = {
4455
+ id: `${id}:io`,
4456
+ basePorts: ports,
4457
+ ioRead: (port) => self.readReg(port - self.base & 31),
4458
+ ioWrite: (port, value) => self.writeReg(port - self.base & 31, value),
4459
+ reset: () => self.ram.fill(0)
4460
+ };
4461
+ }
4462
+ attach(bus) {
4463
+ bus.attachIODevice(this.dev);
4464
+ }
4465
+ reset() {
4466
+ this.ram.fill(0);
4467
+ }
4468
+ readReg(off) {
4469
+ const d = this.now();
4470
+ switch (off) {
4471
+ case 0:
4472
+ return 0;
4473
+ // thousandths of a second
4474
+ case 1:
4475
+ return bcd(Math.floor(d.getMilliseconds() / 10));
4476
+ // hundredths
4477
+ case 2:
4478
+ return bcd(d.getSeconds());
4479
+ case 3:
4480
+ return bcd(d.getMinutes());
4481
+ case 4:
4482
+ return bcd(d.getHours());
4483
+ case 5:
4484
+ return bcd(d.getDay() + 1);
4485
+ // day of week, 1–7
4486
+ case 6:
4487
+ return bcd(d.getDate());
4488
+ // day of month
4489
+ case 7:
4490
+ return bcd(d.getMonth() + 1);
4491
+ // month, 1–12
4492
+ default:
4493
+ return this.ram[off] ?? 0;
4494
+ }
4495
+ }
4496
+ writeReg(off, value) {
4497
+ if (off > 7) this.ram[off] = u8(value);
4498
+ }
4499
+ };
4500
+
4501
+ // src/cards/BankRamCard.ts
4502
+ var BankRamCard = class {
4503
+ id;
4504
+ banks;
4505
+ selectPort;
4506
+ current = 0;
4507
+ mem;
4508
+ dev;
4509
+ constructor(id = "bankram", opts = {}) {
4510
+ this.id = id;
4511
+ const window = (opts.window ?? 49152) & 65535;
4512
+ const size = Math.max(1, Math.min(65536, opts.size ?? 16384));
4513
+ const bankCount = Math.max(1, Math.min(256, opts.banks ?? 4));
4514
+ this.selectPort = (opts.selectPort ?? 64) & 255;
4515
+ this.banks = Array.from({ length: bankCount }, () => new Uint8Array(size));
4516
+ const self = this;
4517
+ this.mem = {
4518
+ id: `${id}:ram`,
4519
+ baseAddress: window,
4520
+ size,
4521
+ readOnly: false,
4522
+ read: (offset) => self.banks[self.current][offset] ?? 255,
4523
+ write: (offset, value) => {
4524
+ self.banks[self.current][offset] = u8(value);
4525
+ },
4526
+ reset: () => {
4527
+ for (const b of self.banks) b.fill(0);
4528
+ self.current = 0;
4529
+ }
4530
+ };
4531
+ this.dev = {
4532
+ id: `${id}:sel`,
4533
+ basePorts: [this.selectPort],
4534
+ ioRead: () => self.current & 255,
4535
+ ioWrite: (_port, value) => {
4536
+ self.current = (value & 255) % self.banks.length;
4537
+ },
4538
+ reset: () => {
4539
+ self.current = 0;
4540
+ }
4541
+ };
4542
+ }
4543
+ attach(bus) {
4544
+ bus.attachMemory(this.mem);
4545
+ bus.attachIODevice(this.dev);
4546
+ }
4547
+ reset() {
4548
+ for (const b of this.banks) b.fill(0);
4549
+ this.current = 0;
4550
+ }
4551
+ };
4552
+
4553
+ // src/bundles/kernels.ts
4554
+ var u83 = (v, fallback) => typeof v === "number" ? v & 255 : fallback;
4555
+ var u163 = (v, fallback) => typeof v === "number" ? v & 65535 : fallback;
4556
+ var serialKernel = {
4557
+ id: "serial",
4558
+ label: "Serial UART (console)",
4559
+ type: "serial",
4560
+ binding: "terminal",
4561
+ configSchema: {
4562
+ dataPort: { type: "u8", default: 16, min: 0, max: 255, description: "Data register port" },
4563
+ ctrlPort: { type: "u8", default: 17, min: 0, max: 255, description: "Status/control port" },
4564
+ chip: { type: "enum", default: "i8251", enum: ["i8251", "m6850"], description: "UART chip" }
4565
+ },
4566
+ create: (id, cfg) => new SerialCard(id, {
4567
+ dataPort: u83(cfg.dataPort, 16),
4568
+ ctrlPort: u83(cfg.ctrlPort, 17),
4569
+ chip: cfg.chip ?? "i8251"
4570
+ }),
4571
+ claims: (cfg) => ({ ports: [u83(cfg.dataPort, 16), u83(cfg.ctrlPort, 17)] })
4572
+ };
4573
+ var parallelKernel = {
4574
+ id: "parallel",
4575
+ label: "Parallel I/O port",
4576
+ type: "parallel",
4577
+ binding: "gpio",
4578
+ configSchema: {
4579
+ port: { type: "u8", default: 0, min: 0, max: 255, description: "I/O port" },
4580
+ direction: { type: "enum", default: "out", enum: ["out", "in", "inout"], description: "Data direction" }
4581
+ },
4582
+ create: (id, cfg) => new ParallelCard(id, { port: u83(cfg.port, 0), direction: cfg.direction ?? "out" }),
4583
+ claims: (cfg) => ({ ports: [u83(cfg.port, 0)] })
4584
+ };
4585
+ var keyboardKernel = {
4586
+ id: "keyboard",
4587
+ label: "Keyboard input port (ASCII)",
4588
+ type: "keyboard",
4589
+ binding: "keyboard",
4590
+ configSchema: {
4591
+ dataPort: { type: "u8", default: 1, min: 0, max: 255, description: "Key data register port" },
4592
+ statusPort: { type: "u8", default: 0, min: 0, max: 255, description: "Key-ready status port" },
4593
+ readyMask: { type: "u8", default: 1, min: 0, max: 255, description: "Status bits set while a key waits" }
4594
+ },
4595
+ create: (id, cfg) => new KeyboardCard(id, {
4596
+ dataPort: u83(cfg.dataPort, 1),
4597
+ statusPort: u83(cfg.statusPort, 0),
4598
+ readyMask: u83(cfg.readyMask, 1)
4599
+ }),
4600
+ claims: (cfg) => ({ ports: [u83(cfg.dataPort, 1), u83(cfg.statusPort, 0)] })
4601
+ };
4602
+ var vdmKernel = {
4603
+ id: "vdm-video",
4604
+ label: "VDM-1 video (64\xD716 characters)",
4605
+ type: "video",
4606
+ binding: "display",
4607
+ configSchema: {
4608
+ base: { type: "u16", default: 52224, min: 0, max: 65535, description: "Video RAM base" }
4609
+ },
4610
+ create: (id, cfg) => new VdmCard(id, { base: u163(cfg.base, 52224) }),
4611
+ claims: () => ({ ports: [] }),
4612
+ memory: (cfg) => [{ id: "vram", base: u163(cfg.base, 52224), size: 1024, kind: "ram" }]
4613
+ };
4614
+ var dazzlerKernel = {
4615
+ id: "dazzler-video",
4616
+ label: "Cromemco Dazzler (colour graphics)",
4617
+ type: "video",
4618
+ binding: "display",
4619
+ configSchema: {
4620
+ controlPort: { type: "u8", default: 14, min: 0, max: 255, description: "Control port (picture on + buffer page)" },
4621
+ formatPort: { type: "u8", default: 15, min: 0, max: 255, description: "Format port (resolution / colour)" }
4622
+ },
4623
+ create: (id, cfg) => new DazzlerCard(id, { controlPort: u83(cfg.controlPort, 14), formatPort: u83(cfg.formatPort, 15) }),
4624
+ claims: (cfg) => ({ ports: [u83(cfg.controlPort, 14), u83(cfg.formatPort, 15)] })
4625
+ // No `memory`: the Dazzler DMAs ordinary system RAM, so it declares no region.
4626
+ };
4627
+ var rtcKernel = {
4628
+ id: "mm58167-rtc",
4629
+ label: "MM58167 real-time clock",
4630
+ type: "rtc",
4631
+ binding: "clock",
4632
+ configSchema: {
4633
+ base: { type: "u8", default: 64, min: 0, max: 224, description: "Base I/O port (occupies base..base+0x1F)" }
4634
+ },
4635
+ create: (id, cfg, ctx) => new RtcCard(id, { base: u83(cfg.base, 64) }, ctx.services.clock),
4636
+ // 32 consecutive registers.
4637
+ claims: (cfg) => {
4638
+ const b = u83(cfg.base, 64);
4639
+ return { ports: Array.from({ length: 32 }, (_v, i) => b + i & 255) };
4640
+ }
4641
+ };
4642
+ var bankRamKernel = {
4643
+ id: "bank-ram",
4644
+ label: "Bank-switching RAM (MMU)",
4645
+ type: "memory",
4646
+ configSchema: {
4647
+ window: { type: "u16", default: 49152, min: 0, max: 65535, description: "Switched window base address" },
4648
+ size: { type: "u16", default: 16384, min: 1, max: 65535, description: "Bytes visible per bank" },
4649
+ banks: { type: "u8", default: 4, min: 1, max: 255, description: "Number of RAM banks" },
4650
+ selectPort: { type: "u8", default: 64, min: 0, max: 255, description: "Bank-select I/O port" }
4651
+ },
4652
+ create: (id, cfg) => new BankRamCard(id, {
4653
+ window: u163(cfg.window, 49152),
4654
+ size: u163(cfg.size, 16384),
4655
+ banks: u83(cfg.banks, 4),
4656
+ selectPort: u83(cfg.selectPort, 64)
4657
+ }),
4658
+ claims: (cfg) => ({ ports: [u83(cfg.selectPort, 64)] })
4659
+ };
4660
+ var kernels = [
4661
+ serialKernel,
4662
+ parallelKernel,
4663
+ keyboardKernel,
4664
+ vdmKernel,
4665
+ dazzlerKernel,
4666
+ rtcKernel,
4667
+ bankRamKernel
4668
+ ];
4669
+ var kernelById = (id) => kernels.find((k) => k.id === id);
4670
+ export {
4671
+ BankRamCard,
4672
+ Bus,
4673
+ CardConfigError,
4674
+ Cpu8080,
4675
+ CpuZ80,
4676
+ DazzlerCard,
4677
+ FdcPlusClient,
4678
+ Flags,
4679
+ FlagsZ80,
4680
+ ImmediateClock,
4681
+ ImsaiMioCard,
4682
+ ImsaiSioCard,
4683
+ InterruptController,
4684
+ IoSpace,
4685
+ KeyboardCard,
4686
+ MachineRunner,
4687
+ MachineSpecError,
4688
+ Mc6850Acia,
4689
+ MemoryMappedIOAdapter,
4690
+ Mits2SioCard,
4691
+ MitsDcddCard,
4692
+ ParallelCard,
4693
+ Port8212,
4694
+ Ram,
4695
+ Registers,
4696
+ RegistersZ80,
4697
+ Rom,
4698
+ RtcCard,
4699
+ SerialCard,
4700
+ SnoopBus,
4701
+ SystemClock,
4702
+ Tr1602Uart,
4703
+ Usart8251,
4704
+ VdmCard,
4705
+ auxCarryAdd,
4706
+ auxCarrySub,
4707
+ bankRamKernel,
4708
+ buildMachine,
4709
+ dazzlerKernel,
4710
+ hi,
4711
+ kernelById,
4712
+ kernels,
4713
+ keyboardKernel,
4714
+ lo,
4715
+ parallelKernel,
4716
+ parityFlag,
4717
+ rtcKernel,
4718
+ seedBundleByName,
4719
+ seedBundles,
4720
+ serialKernel,
4721
+ signBit,
4722
+ toWord,
4723
+ u16,
4724
+ u8,
4725
+ vdmKernel,
4726
+ withDefaults,
4727
+ zeroFlag
4728
+ };