jsbeeb 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (498) hide show
  1. package/.editorconfig +15 -0
  2. package/.git-blame-ignore-revs +3 -0
  3. package/.github/copilot-instructions.md +94 -0
  4. package/.github/workflows/claude-issue-triage.yml +105 -0
  5. package/.github/workflows/claude.yml +63 -0
  6. package/.github/workflows/release-please.yml +75 -0
  7. package/.github/workflows/test-and-deploy.yml +86 -0
  8. package/.gitmodules +6 -0
  9. package/.husky/pre-commit +1 -0
  10. package/.idea/codeStyleSettings.xml +9 -0
  11. package/.idea/codeStyles/Project.xml +62 -0
  12. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  13. package/.idea/compiler.xml +22 -0
  14. package/.idea/copyright/profiles_settings.xml +3 -0
  15. package/.idea/encodings.xml +6 -0
  16. package/.idea/inspectionProfiles/Project_Default.xml +7 -0
  17. package/.idea/jsLibraryMappings.xml +6 -0
  18. package/.idea/jsLinters/jshint.xml +85 -0
  19. package/.idea/jsLinters/jslint.xml +15 -0
  20. package/.idea/jsbeeb.iml +11 -0
  21. package/.idea/misc.xml +6 -0
  22. package/.idea/modules.xml +8 -0
  23. package/.idea/prettier.xml +7 -0
  24. package/.idea/runConfigurations/Debug.xml +5 -0
  25. package/.idea/scopes/scope_settings.xml +5 -0
  26. package/.idea/vcs.xml +8 -0
  27. package/.prettierignore +4 -0
  28. package/.prettierrc.json +1 -0
  29. package/.release-please-manifest.json +3 -0
  30. package/.vscode/launch.json +14 -0
  31. package/.vscode/settings.json +6 -0
  32. package/CHANGELOG.md +32 -0
  33. package/CLAUDE.md +136 -0
  34. package/COPYING +674 -0
  35. package/Dockerfile +22 -0
  36. package/Makefile +30 -0
  37. package/README.md +259 -0
  38. package/docker/nginx-default.conf +10 -0
  39. package/docs/pal-comb-filter-research.md +129 -0
  40. package/docs/pal-simulation-design.md +368 -0
  41. package/eslint.config.js +35 -0
  42. package/index.html +954 -0
  43. package/jsconfig.json +10 -0
  44. package/package.json +102 -0
  45. package/public/discs/README.Irq-Timing +3 -0
  46. package/public/discs/README.bcdtest +5 -0
  47. package/public/discs/README.elite +6 -0
  48. package/public/discs/README.eng_test +3 -0
  49. package/public/discs/README.protection +7 -0
  50. package/public/favicon.ico +0 -0
  51. package/public/images/botbar.png +0 -0
  52. package/public/images/cub-monitor.png +0 -0
  53. package/public/images/jsbeeb-example.png +0 -0
  54. package/public/images/placeholder.png +0 -0
  55. package/public/images/red-off-16.png +0 -0
  56. package/public/images/red-on-16.png +0 -0
  57. package/public/images/sb/CD-left.jpg +0 -0
  58. package/public/images/sb/CD-right.jpg +0 -0
  59. package/public/images/sidebar.png +0 -0
  60. package/public/images/tv.png +0 -0
  61. package/public/images/yellow-off-16.png +0 -0
  62. package/public/images/yellow-on-16.png +0 -0
  63. package/public/jsbeeb-icon.png +0 -0
  64. package/public/robots.txt +3 -0
  65. package/public/roms/ADFS1-53.rom +0 -0
  66. package/public/roms/BASIC.ROM +0 -0
  67. package/public/roms/README +4 -0
  68. package/public/roms/a01/BASIC1.rom +0 -0
  69. package/public/roms/ample.rom +0 -0
  70. package/public/roms/ats-3.0.rom +0 -0
  71. package/public/roms/b/DFS-0.9.rom +0 -0
  72. package/public/roms/b/DFS-1.2.rom +0 -0
  73. package/public/roms/b1770/dfs1770.rom +0 -0
  74. package/public/roms/b1770/zADFS.ROM +0 -0
  75. package/public/roms/bp/dfs.rom +0 -0
  76. package/public/roms/bp/zADFS.ROM +0 -0
  77. package/public/roms/bpos.rom +0 -0
  78. package/public/roms/compact/adfs210.rom +0 -0
  79. package/public/roms/compact/basic48.rom +0 -0
  80. package/public/roms/compact/basic486.rom +0 -0
  81. package/public/roms/compact/os51.rom +0 -0
  82. package/public/roms/compact/utils.rom +0 -0
  83. package/public/roms/deos.rom +0 -0
  84. package/public/roms/master/anfs-4.25.rom +0 -0
  85. package/public/roms/master/mos.txt +68819 -0
  86. package/public/roms/master/mos3.20 +0 -0
  87. package/public/roms/os.rom +0 -0
  88. package/public/roms/os01.rom +0 -0
  89. package/public/roms/tube/6502Tube.rom +0 -0
  90. package/public/roms/tube/ARMeval_100.rom +0 -0
  91. package/public/roms/tube/BIOS.ROM +0 -0
  92. package/public/roms/tube/ReCo6502ROM_816 +0 -0
  93. package/public/roms/tube/Z80_120.rom +0 -0
  94. package/public/roms/us/USBASIC.rom +0 -0
  95. package/public/roms/us/USDNFS.rom +0 -0
  96. package/public/roms/usmos.rom +0 -0
  97. package/public/sounds/disc525/motor.wav +0 -0
  98. package/public/sounds/disc525/motoroff.wav +0 -0
  99. package/public/sounds/disc525/motoron.wav +0 -0
  100. package/public/sounds/disc525/seek.wav +0 -0
  101. package/public/sounds/disc525/seek2.wav +0 -0
  102. package/public/sounds/disc525/seek3.wav +0 -0
  103. package/public/sounds/disc525/step.wav +0 -0
  104. package/public/teletext/txt0.dat +0 -0
  105. package/public/teletext/txt1.dat +0 -0
  106. package/public/teletext/txt2.dat +0 -0
  107. package/public/teletext/txt3.dat +0 -0
  108. package/release-please-config.json +13 -0
  109. package/run-container.sh +92 -0
  110. package/src/6502.js +1347 -0
  111. package/src/6502.opcodes.js +1411 -0
  112. package/src/acia.js +261 -0
  113. package/src/adc.js +149 -0
  114. package/src/analogue-source.js +21 -0
  115. package/src/app/app.js +175 -0
  116. package/src/app/electron.js +20 -0
  117. package/src/app/preload.js +8 -0
  118. package/src/app-bench.js +33 -0
  119. package/src/basic/multiline-tetris +9 -0
  120. package/src/basic-tokenise.js +104 -0
  121. package/src/canvas.js +177 -0
  122. package/src/cmos.js +141 -0
  123. package/src/config.js +165 -0
  124. package/src/ddnoise.js +138 -0
  125. package/src/disc-drive.js +371 -0
  126. package/src/disc-hfe.js +396 -0
  127. package/src/disc.js +997 -0
  128. package/src/econet/L3FS.dat +0 -0
  129. package/src/econet/scsi.dat +0 -0
  130. package/src/econet.js +714 -0
  131. package/src/fake6502.js +32 -0
  132. package/src/fdc.js +248 -0
  133. package/src/filestore.js +666 -0
  134. package/src/gamepad-source.js +59 -0
  135. package/src/gamepads.js +268 -0
  136. package/src/google-drive.js +160 -0
  137. package/src/intel-fdc.js +1717 -0
  138. package/src/jsbeeb.css +363 -0
  139. package/src/keyboard.js +411 -0
  140. package/src/lib/README +1 -0
  141. package/src/lib/webgl-debug.js +911 -0
  142. package/src/main.js +1759 -0
  143. package/src/microphone-input.js +149 -0
  144. package/src/models.js +200 -0
  145. package/src/mouse-joystick-source.js +107 -0
  146. package/src/music5000-worklet.js +43 -0
  147. package/src/music5000.js +207 -0
  148. package/src/scheduler.js +148 -0
  149. package/src/serial.js +31 -0
  150. package/src/soundchip.js +314 -0
  151. package/src/sth.js +59 -0
  152. package/src/tapes.js +265 -0
  153. package/src/teletext.js +348 -0
  154. package/src/teletext_adaptor.js +172 -0
  155. package/src/teletext_data.js +1064 -0
  156. package/src/touchscreen.js +86 -0
  157. package/src/tube.js +349 -0
  158. package/src/url-params.js +256 -0
  159. package/src/utils.js +1090 -0
  160. package/src/via.js +702 -0
  161. package/src/video-filters/pal-composite.js +94 -0
  162. package/src/video-filters/passthrough-filter.js +70 -0
  163. package/src/video-filters/shaders/pal-composite.frag.glsl +147 -0
  164. package/src/video-filters/shaders/pal-composite.vert.glsl +8 -0
  165. package/src/video-filters/shaders/passthrough.frag.glsl +6 -0
  166. package/src/video-filters/shaders/passthrough.vert.glsl +7 -0
  167. package/src/video.js +794 -0
  168. package/src/wd-fdc.js +1344 -0
  169. package/src/web/audio-handler.js +146 -0
  170. package/src/web/audio-renderer.js +115 -0
  171. package/src/web/debug.js +529 -0
  172. package/tests/integration/RmwX.asm +47 -0
  173. package/tests/integration/TestInstructionsSource +27 -0
  174. package/tests/integration/TestTimingsResults +27 -0
  175. package/tests/integration/TestTimingsSource +61 -0
  176. package/tests/integration/bcd.js +23 -0
  177. package/tests/integration/dormann.js +101 -0
  178. package/tests/integration/dp111timing.js +42 -0
  179. package/tests/integration/ensure-submodules.js +25 -0
  180. package/tests/integration/nops.bas +119 -0
  181. package/tests/integration/nops.js +24 -0
  182. package/tests/integration/protection.js +26 -0
  183. package/tests/integration/rmw.js +69 -0
  184. package/tests/integration/teletext/expected_bug_469.png +0 -0
  185. package/tests/integration/teletext/expected_flash_0.png +0 -0
  186. package/tests/integration/teletext/expected_flash_1.png +0 -0
  187. package/tests/integration/teletext/expected_hoglet_held_char.png +0 -0
  188. package/tests/integration/teletext/expected_reveal_flash_0.png +0 -0
  189. package/tests/integration/teletext/expected_reveal_flash_1.png +0 -0
  190. package/tests/integration/teletext.js +126 -0
  191. package/tests/integration/timings.js +56 -0
  192. package/tests/integration/via.js +1125 -0
  193. package/tests/suite/README.md +7 -0
  194. package/tests/suite/Test Suite 2.15.txt +373 -0
  195. package/tests/suite/bin/ start +0 -0
  196. package/tests/suite/bin/adca +0 -0
  197. package/tests/suite/bin/adcax +0 -0
  198. package/tests/suite/bin/adcay +0 -0
  199. package/tests/suite/bin/adcb +0 -0
  200. package/tests/suite/bin/adcix +0 -0
  201. package/tests/suite/bin/adciy +0 -0
  202. package/tests/suite/bin/adcz +0 -0
  203. package/tests/suite/bin/adczx +0 -0
  204. package/tests/suite/bin/alrb +0 -0
  205. package/tests/suite/bin/ancb +0 -0
  206. package/tests/suite/bin/anda +0 -0
  207. package/tests/suite/bin/andax +0 -0
  208. package/tests/suite/bin/anday +0 -0
  209. package/tests/suite/bin/andb +0 -0
  210. package/tests/suite/bin/andix +0 -0
  211. package/tests/suite/bin/andiy +0 -0
  212. package/tests/suite/bin/andz +0 -0
  213. package/tests/suite/bin/andzx +0 -0
  214. package/tests/suite/bin/aneb +0 -0
  215. package/tests/suite/bin/arrb +0 -0
  216. package/tests/suite/bin/asla +0 -0
  217. package/tests/suite/bin/aslax +0 -0
  218. package/tests/suite/bin/asln +0 -0
  219. package/tests/suite/bin/aslz +0 -0
  220. package/tests/suite/bin/aslzx +0 -0
  221. package/tests/suite/bin/asoa +0 -0
  222. package/tests/suite/bin/asoax +0 -0
  223. package/tests/suite/bin/asoay +0 -0
  224. package/tests/suite/bin/asoix +0 -0
  225. package/tests/suite/bin/asoiy +0 -0
  226. package/tests/suite/bin/asoz +0 -0
  227. package/tests/suite/bin/asozx +0 -0
  228. package/tests/suite/bin/axsa +0 -0
  229. package/tests/suite/bin/axsix +0 -0
  230. package/tests/suite/bin/axsz +0 -0
  231. package/tests/suite/bin/axszy +0 -0
  232. package/tests/suite/bin/bccr +0 -0
  233. package/tests/suite/bin/bcsr +0 -0
  234. package/tests/suite/bin/beqr +0 -0
  235. package/tests/suite/bin/bita +0 -0
  236. package/tests/suite/bin/bitz +0 -0
  237. package/tests/suite/bin/bmir +0 -0
  238. package/tests/suite/bin/bner +0 -0
  239. package/tests/suite/bin/bplr +0 -0
  240. package/tests/suite/bin/branchwrap +0 -0
  241. package/tests/suite/bin/brkn +0 -0
  242. package/tests/suite/bin/bvcr +0 -0
  243. package/tests/suite/bin/bvsr +0 -0
  244. package/tests/suite/bin/cia1pb6 +0 -0
  245. package/tests/suite/bin/cia1pb7 +0 -0
  246. package/tests/suite/bin/cia1ta +0 -0
  247. package/tests/suite/bin/cia1tab +0 -0
  248. package/tests/suite/bin/cia1tb +0 -0
  249. package/tests/suite/bin/cia1tb123 +0 -0
  250. package/tests/suite/bin/cia2pb6 +0 -0
  251. package/tests/suite/bin/cia2pb7 +0 -0
  252. package/tests/suite/bin/cia2ta +0 -0
  253. package/tests/suite/bin/cia2tb +0 -0
  254. package/tests/suite/bin/cia2tb123 +0 -0
  255. package/tests/suite/bin/clcn +0 -0
  256. package/tests/suite/bin/cldn +0 -0
  257. package/tests/suite/bin/clin +0 -0
  258. package/tests/suite/bin/clvn +0 -0
  259. package/tests/suite/bin/cmpa +0 -0
  260. package/tests/suite/bin/cmpax +0 -0
  261. package/tests/suite/bin/cmpay +0 -0
  262. package/tests/suite/bin/cmpb +0 -0
  263. package/tests/suite/bin/cmpix +0 -0
  264. package/tests/suite/bin/cmpiy +0 -0
  265. package/tests/suite/bin/cmpz +0 -0
  266. package/tests/suite/bin/cmpzx +0 -0
  267. package/tests/suite/bin/cntdef +0 -0
  268. package/tests/suite/bin/cnto2 +0 -0
  269. package/tests/suite/bin/cpuport +0 -0
  270. package/tests/suite/bin/cputiming +0 -0
  271. package/tests/suite/bin/cpxa +0 -0
  272. package/tests/suite/bin/cpxb +0 -0
  273. package/tests/suite/bin/cpxz +0 -0
  274. package/tests/suite/bin/cpya +0 -0
  275. package/tests/suite/bin/cpyb +0 -0
  276. package/tests/suite/bin/cpyz +0 -0
  277. package/tests/suite/bin/dcma +0 -0
  278. package/tests/suite/bin/dcmax +0 -0
  279. package/tests/suite/bin/dcmay +0 -0
  280. package/tests/suite/bin/dcmix +0 -0
  281. package/tests/suite/bin/dcmiy +0 -0
  282. package/tests/suite/bin/dcmz +0 -0
  283. package/tests/suite/bin/dcmzx +0 -0
  284. package/tests/suite/bin/deca +0 -0
  285. package/tests/suite/bin/decax +0 -0
  286. package/tests/suite/bin/decz +0 -0
  287. package/tests/suite/bin/deczx +0 -0
  288. package/tests/suite/bin/dexn +0 -0
  289. package/tests/suite/bin/deyn +0 -0
  290. package/tests/suite/bin/eora +0 -0
  291. package/tests/suite/bin/eorax +0 -0
  292. package/tests/suite/bin/eoray +0 -0
  293. package/tests/suite/bin/eorb +0 -0
  294. package/tests/suite/bin/eorix +0 -0
  295. package/tests/suite/bin/eoriy +0 -0
  296. package/tests/suite/bin/eorz +0 -0
  297. package/tests/suite/bin/eorzx +0 -0
  298. package/tests/suite/bin/finish +0 -0
  299. package/tests/suite/bin/flipos +0 -0
  300. package/tests/suite/bin/icr01 +0 -0
  301. package/tests/suite/bin/imr +0 -0
  302. package/tests/suite/bin/inca +0 -0
  303. package/tests/suite/bin/incax +0 -0
  304. package/tests/suite/bin/incz +0 -0
  305. package/tests/suite/bin/inczx +0 -0
  306. package/tests/suite/bin/insa +0 -0
  307. package/tests/suite/bin/insax +0 -0
  308. package/tests/suite/bin/insay +0 -0
  309. package/tests/suite/bin/insix +0 -0
  310. package/tests/suite/bin/insiy +0 -0
  311. package/tests/suite/bin/insz +0 -0
  312. package/tests/suite/bin/inszx +0 -0
  313. package/tests/suite/bin/inxn +0 -0
  314. package/tests/suite/bin/inyn +0 -0
  315. package/tests/suite/bin/irq +0 -0
  316. package/tests/suite/bin/jmpi +0 -0
  317. package/tests/suite/bin/jmpw +0 -0
  318. package/tests/suite/bin/jsrw +0 -0
  319. package/tests/suite/bin/lasay +0 -0
  320. package/tests/suite/bin/laxa +0 -0
  321. package/tests/suite/bin/laxay +0 -0
  322. package/tests/suite/bin/laxix +0 -0
  323. package/tests/suite/bin/laxiy +0 -0
  324. package/tests/suite/bin/laxz +0 -0
  325. package/tests/suite/bin/laxzy +0 -0
  326. package/tests/suite/bin/ldaa +0 -0
  327. package/tests/suite/bin/ldaax +0 -0
  328. package/tests/suite/bin/ldaay +0 -0
  329. package/tests/suite/bin/ldab +0 -0
  330. package/tests/suite/bin/ldaix +0 -0
  331. package/tests/suite/bin/ldaiy +0 -0
  332. package/tests/suite/bin/ldaz +0 -0
  333. package/tests/suite/bin/ldazx +0 -0
  334. package/tests/suite/bin/ldxa +0 -0
  335. package/tests/suite/bin/ldxay +0 -0
  336. package/tests/suite/bin/ldxb +0 -0
  337. package/tests/suite/bin/ldxz +0 -0
  338. package/tests/suite/bin/ldxzy +0 -0
  339. package/tests/suite/bin/ldya +0 -0
  340. package/tests/suite/bin/ldyax +0 -0
  341. package/tests/suite/bin/ldyb +0 -0
  342. package/tests/suite/bin/ldyz +0 -0
  343. package/tests/suite/bin/ldyzx +0 -0
  344. package/tests/suite/bin/loadth +0 -0
  345. package/tests/suite/bin/lsea +0 -0
  346. package/tests/suite/bin/lseax +0 -0
  347. package/tests/suite/bin/lseay +0 -0
  348. package/tests/suite/bin/lseix +0 -0
  349. package/tests/suite/bin/lseiy +0 -0
  350. package/tests/suite/bin/lsez +0 -0
  351. package/tests/suite/bin/lsezx +0 -0
  352. package/tests/suite/bin/lsra +0 -0
  353. package/tests/suite/bin/lsrax +0 -0
  354. package/tests/suite/bin/lsrn +0 -0
  355. package/tests/suite/bin/lsrz +0 -0
  356. package/tests/suite/bin/lsrzx +0 -0
  357. package/tests/suite/bin/lxab +0 -0
  358. package/tests/suite/bin/mmu +0 -0
  359. package/tests/suite/bin/mmufetch +0 -0
  360. package/tests/suite/bin/nmi +0 -0
  361. package/tests/suite/bin/nopa +0 -0
  362. package/tests/suite/bin/nopax +0 -0
  363. package/tests/suite/bin/nopb +0 -0
  364. package/tests/suite/bin/nopn +0 -0
  365. package/tests/suite/bin/nopz +0 -0
  366. package/tests/suite/bin/nopzx +0 -0
  367. package/tests/suite/bin/oneshot +0 -0
  368. package/tests/suite/bin/oraa +0 -0
  369. package/tests/suite/bin/oraax +0 -0
  370. package/tests/suite/bin/oraay +0 -0
  371. package/tests/suite/bin/orab +0 -0
  372. package/tests/suite/bin/oraix +0 -0
  373. package/tests/suite/bin/oraiy +0 -0
  374. package/tests/suite/bin/oraz +0 -0
  375. package/tests/suite/bin/orazx +0 -0
  376. package/tests/suite/bin/phan +0 -0
  377. package/tests/suite/bin/phpn +0 -0
  378. package/tests/suite/bin/plan +0 -0
  379. package/tests/suite/bin/plpn +0 -0
  380. package/tests/suite/bin/rlaa +0 -0
  381. package/tests/suite/bin/rlaax +0 -0
  382. package/tests/suite/bin/rlaay +0 -0
  383. package/tests/suite/bin/rlaix +0 -0
  384. package/tests/suite/bin/rlaiy +0 -0
  385. package/tests/suite/bin/rlaz +0 -0
  386. package/tests/suite/bin/rlazx +0 -0
  387. package/tests/suite/bin/rola +0 -0
  388. package/tests/suite/bin/rolax +0 -0
  389. package/tests/suite/bin/roln +0 -0
  390. package/tests/suite/bin/rolz +0 -0
  391. package/tests/suite/bin/rolzx +0 -0
  392. package/tests/suite/bin/rora +0 -0
  393. package/tests/suite/bin/rorax +0 -0
  394. package/tests/suite/bin/rorn +0 -0
  395. package/tests/suite/bin/rorz +0 -0
  396. package/tests/suite/bin/rorzx +0 -0
  397. package/tests/suite/bin/rraa +0 -0
  398. package/tests/suite/bin/rraax +0 -0
  399. package/tests/suite/bin/rraay +0 -0
  400. package/tests/suite/bin/rraix +0 -0
  401. package/tests/suite/bin/rraiy +0 -0
  402. package/tests/suite/bin/rraz +0 -0
  403. package/tests/suite/bin/rrazx +0 -0
  404. package/tests/suite/bin/rtin +0 -0
  405. package/tests/suite/bin/rtsn +0 -0
  406. package/tests/suite/bin/sbca +0 -0
  407. package/tests/suite/bin/sbcax +0 -0
  408. package/tests/suite/bin/sbcay +0 -0
  409. package/tests/suite/bin/sbcb +0 -0
  410. package/tests/suite/bin/sbcb(eb) +0 -0
  411. package/tests/suite/bin/sbcix +0 -0
  412. package/tests/suite/bin/sbciy +0 -0
  413. package/tests/suite/bin/sbcz +0 -0
  414. package/tests/suite/bin/sbczx +0 -0
  415. package/tests/suite/bin/sbxb +0 -0
  416. package/tests/suite/bin/secn +0 -0
  417. package/tests/suite/bin/sedn +0 -0
  418. package/tests/suite/bin/sein +0 -0
  419. package/tests/suite/bin/shaay +0 -0
  420. package/tests/suite/bin/shaiy +0 -0
  421. package/tests/suite/bin/shsay +0 -0
  422. package/tests/suite/bin/shxay +0 -0
  423. package/tests/suite/bin/shyax +0 -0
  424. package/tests/suite/bin/staa +0 -0
  425. package/tests/suite/bin/staax +0 -0
  426. package/tests/suite/bin/staay +0 -0
  427. package/tests/suite/bin/staix +0 -0
  428. package/tests/suite/bin/staiy +0 -0
  429. package/tests/suite/bin/staz +0 -0
  430. package/tests/suite/bin/stazx +0 -0
  431. package/tests/suite/bin/stxa +0 -0
  432. package/tests/suite/bin/stxz +0 -0
  433. package/tests/suite/bin/stxzy +0 -0
  434. package/tests/suite/bin/stya +0 -0
  435. package/tests/suite/bin/styz +0 -0
  436. package/tests/suite/bin/styzx +0 -0
  437. package/tests/suite/bin/taxn +0 -0
  438. package/tests/suite/bin/tayn +0 -0
  439. package/tests/suite/bin/trap1 +0 -0
  440. package/tests/suite/bin/trap10 +0 -0
  441. package/tests/suite/bin/trap11 +0 -0
  442. package/tests/suite/bin/trap12 +0 -0
  443. package/tests/suite/bin/trap13 +0 -0
  444. package/tests/suite/bin/trap14 +0 -0
  445. package/tests/suite/bin/trap15 +0 -0
  446. package/tests/suite/bin/trap16 +0 -0
  447. package/tests/suite/bin/trap17 +0 -0
  448. package/tests/suite/bin/trap2 +0 -0
  449. package/tests/suite/bin/trap3 +0 -0
  450. package/tests/suite/bin/trap4 +0 -0
  451. package/tests/suite/bin/trap5 +0 -0
  452. package/tests/suite/bin/trap6 +0 -0
  453. package/tests/suite/bin/trap7 +0 -0
  454. package/tests/suite/bin/trap8 +0 -0
  455. package/tests/suite/bin/trap9 +0 -0
  456. package/tests/suite/bin/tsxn +0 -0
  457. package/tests/suite/bin/txan +0 -0
  458. package/tests/suite/bin/txsn +0 -0
  459. package/tests/suite/bin/tyan +0 -0
  460. package/tests/suite/cbm-hackers-post.html +178 -0
  461. package/tests/suite/cbm-hackers-post.md +78 -0
  462. package/tests/test-machine.js +288 -0
  463. package/tests/test-suite.js +147 -0
  464. package/tests/test.css +7 -0
  465. package/tests/unit/gzip/test-1 +0 -0
  466. package/tests/unit/gzip/test-1.gz +0 -0
  467. package/tests/unit/gzip/test-2 +0 -0
  468. package/tests/unit/gzip/test-2.gz +0 -0
  469. package/tests/unit/gzip/test-3 +0 -0
  470. package/tests/unit/gzip/test-3.gz +0 -0
  471. package/tests/unit/gzip/test-4 +0 -0
  472. package/tests/unit/gzip/test-4.gz +0 -0
  473. package/tests/unit/test-adc.js +307 -0
  474. package/tests/unit/test-bcd.js +30 -0
  475. package/tests/unit/test-cmos.js +266 -0
  476. package/tests/unit/test-disc-drive.js +85 -0
  477. package/tests/unit/test-disc-hfe.js +347 -0
  478. package/tests/unit/test-disc.js +232 -0
  479. package/tests/unit/test-fifo.js +35 -0
  480. package/tests/unit/test-gamepad-source.js +67 -0
  481. package/tests/unit/test-gzip.js +22 -0
  482. package/tests/unit/test-intel-fdc.js +93 -0
  483. package/tests/unit/test-keyboard.js +410 -0
  484. package/tests/unit/test-mouse-joystick-source.js +128 -0
  485. package/tests/unit/test-scheduler.js +190 -0
  486. package/tests/unit/test-serial.js +154 -0
  487. package/tests/unit/test-teletext-adaptor.js +359 -0
  488. package/tests/unit/test-tokenise.js +65 -0
  489. package/tests/unit/test-url-params.js +398 -0
  490. package/tests/unit/test-utils.js +276 -0
  491. package/tests/unit/test-video.js +498 -0
  492. package/tests/unit/test-zip.js +56 -0
  493. package/tests/unit/zip/test-mixed.zip +0 -0
  494. package/tests/unit/zip/test-rom.zip +0 -0
  495. package/tests/unit/zip/test-ssd.zip +0 -0
  496. package/tools/fir-generator.js +80 -0
  497. package/tools/vite-plugin-fir-shader.js +131 -0
  498. package/vite.config.js +34 -0
package/src/video.js ADDED
@@ -0,0 +1,794 @@
1
+ "use strict";
2
+ import { Teletext } from "./teletext.js";
3
+ import * as utils from "./utils.js";
4
+
5
+ export const VDISPENABLE = 1 << 0;
6
+ export const HDISPENABLE = 1 << 1;
7
+ export const SKEWDISPENABLE = 1 << 2;
8
+ export const SCANLINEDISPENABLE = 1 << 3;
9
+ export const USERDISPENABLE = 1 << 4;
10
+ export const FRAMESKIPENABLE = 1 << 5;
11
+ export const EVERYTHINGENABLED =
12
+ VDISPENABLE | HDISPENABLE | SKEWDISPENABLE | SCANLINEDISPENABLE | USERDISPENABLE | FRAMESKIPENABLE;
13
+
14
+ export const OPAQUE_BLACK = 0xff000000;
15
+ export const OPAQUE_WHITE = 0xffffffff;
16
+
17
+ ////////////////////
18
+ // ULA interface
19
+ class Ula {
20
+ constructor(video) {
21
+ this.video = video;
22
+ }
23
+
24
+ write(addr, val) {
25
+ addr |= 0;
26
+ val |= 0;
27
+ if (addr & 1) {
28
+ const index = (val >>> 4) & 0xf;
29
+ this.video.actualPal[index] = val & 0xf;
30
+ let ulaCol = val & 7;
31
+ if (!(val & 8 && this.video.ulactrl & 1)) ulaCol ^= 7;
32
+ if (this.video.ulaPal[index] !== this.video.collook[ulaCol]) {
33
+ this.video.ulaPal[index] = this.video.collook[ulaCol];
34
+ }
35
+ } else {
36
+ if ((this.video.ulactrl ^ val) & 1) {
37
+ // Flash colour has changed.
38
+ const flashEnabled = !!(val & 1);
39
+ for (let i = 0; i < 16; ++i) {
40
+ let index = this.video.actualPal[i] & 7;
41
+ if (!(flashEnabled && this.video.actualPal[i] & 8)) index ^= 7;
42
+ if (this.video.ulaPal[i] !== this.video.collook[index]) {
43
+ this.video.ulaPal[i] = this.video.collook[index];
44
+ }
45
+ }
46
+ }
47
+ this.video.ulactrl = val;
48
+ this.video.pixelsPerChar = val & 0x10 ? 8 : 16;
49
+ this.video.halfClock = !(val & 0x10);
50
+ const newMode = (val >>> 2) & 3;
51
+ if (newMode !== this.video.ulaMode) {
52
+ this.video.ulaMode = newMode;
53
+ }
54
+ this.video.teletextMode = !!(val & 2);
55
+ }
56
+ }
57
+ }
58
+
59
+ ////////////////////
60
+ // CRTC interface
61
+ class Crtc {
62
+ constructor(video) {
63
+ this.video = video;
64
+ this.curReg = 0;
65
+ this.crtcmask = new Uint8Array([
66
+ 0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f, 0xf3, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff, 0x3f, 0xff,
67
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
68
+ ]);
69
+ }
70
+
71
+ read(addr) {
72
+ if (!(addr & 1)) return 0;
73
+ switch (this.curReg) {
74
+ case 12:
75
+ case 13:
76
+ case 14:
77
+ case 15:
78
+ case 16:
79
+ case 17:
80
+ return this.video.regs[this.curReg];
81
+ }
82
+ return 0;
83
+ }
84
+
85
+ write(addr, val) {
86
+ if (addr & 1) {
87
+ this.video.regs[this.curReg] = val & this.crtcmask[this.curReg];
88
+ switch (this.curReg) {
89
+ case 3:
90
+ this.video.hpulseWidth = val & 0x0f;
91
+ this.video.vpulseWidth = (val & 0xf0) >>> 4;
92
+ break;
93
+ case 8: {
94
+ this.video.interlacedSyncAndVideo = (val & 3) === 3;
95
+ const skew = (val & 0x30) >>> 4;
96
+ if (skew < 3) {
97
+ this.video.displayEnableSkew = skew;
98
+ this.video.dispEnableSet(USERDISPENABLE);
99
+ } else {
100
+ this.video.dispEnableClear(USERDISPENABLE);
101
+ }
102
+ break;
103
+ }
104
+ case 14:
105
+ case 15:
106
+ this.video.cursorPos = (this.video.regs[15] | (this.video.regs[14] << 8)) & 0x3fff;
107
+ break;
108
+ }
109
+ } else this.curReg = val & 31;
110
+ }
111
+ }
112
+
113
+ ////////////////////
114
+ // Misc support functions
115
+
116
+ function debugCopyFb(dest, src) {
117
+ for (let i = 0; i < 1024 * 768; ++i) {
118
+ dest[i] = src[i];
119
+ }
120
+ }
121
+
122
+ function lerp1(a, b, alpha) {
123
+ let val = (b - a) * alpha + a;
124
+ if (val < 0) val = 0;
125
+ if (val > 255) val = 255;
126
+ return val;
127
+ }
128
+
129
+ function lerp(col1, col2, alpha) {
130
+ if (alpha < 0) alpha = 0;
131
+ if (alpha > 1) alpha = 1;
132
+ const r1 = (col1 >>> 16) & 0xff;
133
+ const g1 = (col1 >>> 8) & 0xff;
134
+ const b1 = (col1 >>> 0) & 0xff;
135
+ const r2 = (col2 >>> 16) & 0xff;
136
+ const g2 = (col2 >>> 8) & 0xff;
137
+ const b2 = (col2 >>> 0) & 0xff;
138
+ const red = lerp1(r1, r2, alpha);
139
+ const green = lerp1(g1, g2, alpha);
140
+ const blue = lerp1(b1, b2, alpha);
141
+ return (red << 16) | (green << 8) | blue;
142
+ }
143
+
144
+ function table4bppOffset(ulamode, byte) {
145
+ return (ulamode << 12) | (byte << 4);
146
+ }
147
+
148
+ ////////////////////
149
+ // The video class
150
+ export class Video {
151
+ constructor(isMaster, fb32_param, paint_ext_param) {
152
+ this.isMaster = isMaster;
153
+ this.fb32 = utils.makeFast32(fb32_param);
154
+ this.collook = utils.makeFast32(
155
+ new Uint32Array([
156
+ 0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff,
157
+ ]),
158
+ );
159
+ this.screenAddrSubtract = new Uint8Array([8, 4, 10, 5]);
160
+ this.cursorTable = new Uint8Array([0x00, 0x00, 0x00, 0x80, 0x40, 0x20, 0x20]);
161
+ this.cursorFlashMask = new Uint8Array([0x00, 0x00, 0x08, 0x10]);
162
+ this.regs = new Uint8Array(32);
163
+ this.bitmapX = 0;
164
+ this.bitmapY = 0;
165
+ this.oddClock = false;
166
+ this.frameCount = 0;
167
+ this.doEvenFrameLogic = false;
168
+ this.isEvenRender = true;
169
+ this.lastRenderWasEven = false;
170
+ this.firstScanline = true;
171
+ this.inHSync = false;
172
+ this.inVSync = false;
173
+ this.hadVSyncThisRow = false;
174
+ this.checkVertAdjust = false;
175
+ this.endOfMainLatched = false;
176
+ this.endOfVertAdjustLatched = false;
177
+ this.endOfFrameLatched = false;
178
+ this.inVertAdjust = false;
179
+ this.inDummyRaster = false;
180
+ this.hpulseWidth = 0;
181
+ this.vpulseWidth = 0;
182
+ this.hpulseCounter = 0;
183
+ this.vpulseCounter = 0;
184
+ this.dispEnabled = FRAMESKIPENABLE;
185
+ this.horizCounter = 0;
186
+ this.vertCounter = 0;
187
+ this.scanlineCounter = 0;
188
+ this.vertAdjustCounter = 0;
189
+ this.addr = 0;
190
+ this.lineStartAddr = 0;
191
+ this.nextLineStartAddr = 0;
192
+ this.ulactrl = 0;
193
+ this.pixelsPerChar = 8;
194
+ this.halfClock = false;
195
+ this.ulaMode = 0;
196
+ this.teletextMode = false;
197
+ this.displayEnableSkew = 0;
198
+ this.ulaPal = utils.makeFast32(new Uint32Array(16));
199
+ this.actualPal = new Uint8Array(16);
200
+ this.teletext = new Teletext();
201
+ this.cursorOn = false;
202
+ this.cursorOff = false;
203
+ this.cursorOnThisFrame = false;
204
+ this.cursorDrawIndex = 0;
205
+ this.cursorPos = 0;
206
+ this.interlacedSyncAndVideo = false;
207
+ this.doubledScanlines = true;
208
+ this.frameSkipCount = 0;
209
+ this.screenSubtract = 0;
210
+
211
+ this.topBorder = 12;
212
+ this.bottomBorder = 13;
213
+ this.leftBorder = 5 * 16;
214
+ this.rightBorder = 3 * 16;
215
+
216
+ this.paint_ext = paint_ext_param;
217
+
218
+ this.debugPrevScreen = null;
219
+
220
+ this.table4bpp = (() => {
221
+ const t = new Uint8Array(4 * 256 * 16);
222
+ let i, b, temp, left;
223
+ for (b = 0; b < 256; ++b) {
224
+ temp = b;
225
+ for (i = 0; i < 16; ++i) {
226
+ left = 0;
227
+ if (temp & 2) left |= 1;
228
+ if (temp & 8) left |= 2;
229
+ if (temp & 32) left |= 4;
230
+ if (temp & 128) left |= 8;
231
+ t[table4bppOffset(3, b) + i] = left;
232
+ temp <<= 1;
233
+ temp |= 1;
234
+ }
235
+ for (i = 0; i < 16; ++i) {
236
+ t[table4bppOffset(2, b) + i] = t[table4bppOffset(3, b) + (i >>> 1)];
237
+ t[table4bppOffset(1, b) + i] = t[table4bppOffset(3, b) + (i >>> 2)];
238
+ t[table4bppOffset(0, b) + i] = t[table4bppOffset(3, b) + (i >>> 3)];
239
+ }
240
+ }
241
+ return t;
242
+ })();
243
+
244
+ this.crtc = new Crtc(this);
245
+ this.ula = new Ula(this);
246
+
247
+ this.reset(null);
248
+ this.clearPaintBuffer();
249
+ this.paint();
250
+ }
251
+
252
+ reset(cpu, via) {
253
+ this.cpu = cpu;
254
+ this.sysvia = via;
255
+ if (via) via.cb2changecallback = this.cb2changed.bind(this);
256
+ }
257
+
258
+ paint() {
259
+ this.paint_ext(this.leftBorder, this.topBorder, 1024 - this.rightBorder, 625 - this.bottomBorder);
260
+ }
261
+
262
+ clearPaintBuffer() {
263
+ const fb32 = this.fb32;
264
+ if (this.interlacedSyncAndVideo || !this.doubledScanlines) {
265
+ let line = this.frameCount & 1;
266
+ while (line < 625) {
267
+ const start = line * 1024;
268
+ fb32.fill(OPAQUE_BLACK, start, start + 1024);
269
+ line += 2;
270
+ }
271
+ } else {
272
+ fb32.fill(OPAQUE_BLACK);
273
+ }
274
+ }
275
+
276
+ paintAndClear() {
277
+ if (this.dispEnabled & FRAMESKIPENABLE) {
278
+ this.paint();
279
+ this.clearPaintBuffer();
280
+ }
281
+ this.dispEnabled &= ~FRAMESKIPENABLE;
282
+ let enable = FRAMESKIPENABLE;
283
+ if (this.frameSkipCount > 1) {
284
+ if (this.frameCount % this.frameSkipCount) enable = 0;
285
+ }
286
+ this.dispEnabled |= enable;
287
+
288
+ this.bitmapY = 0;
289
+ // Interlace even frame fires vsync midway through a scanline.
290
+ if (!!(this.regs[8] & 1) && !!(this.frameCount & 1)) {
291
+ this.bitmapY = -1;
292
+ }
293
+ }
294
+
295
+ debugOffset(x, y) {
296
+ if (x < 0 || x >= 1024) return -1;
297
+ if (y < 0 || y >= 768) return -1;
298
+ return y * 1024 + x;
299
+ }
300
+
301
+ debugPaint() {
302
+ if (!this.debugPrevScreen) {
303
+ this.debugPrevScreen = new Uint32Array(1024 * 768);
304
+ }
305
+ debugCopyFb(this.debugPrevScreen, this.fb32);
306
+ const dotSize = 10;
307
+ for (let y = -dotSize; y <= dotSize; y++) {
308
+ for (let x = -dotSize; x <= dotSize; ++x) {
309
+ const dist = Math.sqrt(x * x + y * y) / dotSize;
310
+ if (dist > 1) continue;
311
+ const offset = this.debugOffset(this.bitmapX + x, this.bitmapY + y);
312
+ this.fb32[offset] = lerp(this.fb32[offset], OPAQUE_WHITE, Math.pow(1 - dist, 2));
313
+ }
314
+ }
315
+ this.paint();
316
+ debugCopyFb(this.fb32, this.debugPrevScreen);
317
+ }
318
+
319
+ blitFb(dat, destOffset, numPixels) {
320
+ destOffset |= 0;
321
+ const offset = table4bppOffset(this.ulaMode, dat);
322
+ const fb32 = this.fb32;
323
+ const ulaPal = this.ulaPal;
324
+ const table4bpp = this.table4bpp;
325
+ // Take advantage of numPixels being either 8 or 16
326
+ if (numPixels === 8) {
327
+ for (let i = 0; i < 8; ++i) {
328
+ fb32[destOffset + i] = ulaPal[table4bpp[offset + i]];
329
+ }
330
+ } else {
331
+ for (let i = 0; i < 16; ++i) {
332
+ fb32[destOffset + i] = ulaPal[table4bpp[offset + i]];
333
+ }
334
+ }
335
+ }
336
+
337
+ handleCursor(offset) {
338
+ if (this.cursorOnThisFrame && this.ulactrl & this.cursorTable[this.cursorDrawIndex]) {
339
+ for (let i = 0; i < this.pixelsPerChar; ++i) {
340
+ this.fb32[offset + i] ^= 0x00ffffff;
341
+ }
342
+ if (this.doubledScanlines && !this.interlacedSyncAndVideo) {
343
+ for (let i = 0; i < this.pixelsPerChar; ++i) {
344
+ this.fb32[offset + 1024 + i] ^= 0x00ffffff;
345
+ }
346
+ }
347
+ }
348
+ if (++this.cursorDrawIndex === 7) this.cursorDrawIndex = 0;
349
+ }
350
+
351
+ setScreenHwScroll(viaScreenHwScroll) {
352
+ this.screenSubtract = this.screenAddrSubtract[viaScreenHwScroll];
353
+ }
354
+
355
+ readVideoMem() {
356
+ if (this.addr & 0x2000) {
357
+ // Mode 7 chunky addressing mode if MA13 set.
358
+ // Address offset by scanline is ignored.
359
+ // On model B only, there's a quirk for reading 0x3c00.
360
+ // See: http://www.retrosoftware.co.uk/forum/viewtopic.php?f=73&t=1011
361
+ let memAddr = this.addr & 0x3ff;
362
+ if (this.addr & 0x800 || this.isMaster) {
363
+ memAddr |= 0x7c00;
364
+ } else {
365
+ memAddr |= 0x3c00;
366
+ }
367
+ return this.cpu.videoRead(memAddr);
368
+ } else {
369
+ // Emulate IC32/IC39 address translation: adjust MA11..MA8 on overflow before composing the DRAM address.
370
+ const ma = this.addr & 0x1fff;
371
+ const raLow = this.scanlineCounter & 0x07;
372
+ let adjustedHigh = (ma >>> 8) & 0x0f;
373
+ if (ma & 0x1000) {
374
+ adjustedHigh = (adjustedHigh - this.screenSubtract) & 0x0f;
375
+ }
376
+ const hiResAddr = ((adjustedHigh << 11) | ((ma & 0xff) << 3) | raLow) & 0x7fff;
377
+ return this.cpu.videoRead(hiResAddr);
378
+ }
379
+ }
380
+
381
+ endOfFrame() {
382
+ this.vertCounter = 0;
383
+ this.firstScanline = true;
384
+ this.nextLineStartAddr = (this.regs[13] | (this.regs[12] << 8)) & 0x3fff;
385
+ this.lineStartAddr = this.nextLineStartAddr;
386
+ this.dispEnableSet(VDISPENABLE);
387
+ const cursorFlash = (this.regs[10] & 0x60) >>> 5;
388
+ this.cursorOnThisFrame = cursorFlash === 0 || !!(this.frameCount & this.cursorFlashMask[cursorFlash]);
389
+ this.lastRenderWasEven = this.isEvenRender;
390
+ this.isEvenRender = !(this.frameCount & 1);
391
+ if (!this.inVSync) {
392
+ this.doEvenFrameLogic = false;
393
+ }
394
+ }
395
+
396
+ endOfCharacterLine() {
397
+ this.vertCounter = (this.vertCounter + 1) & 0x7f;
398
+
399
+ this.scanlineCounter = 0;
400
+ this.hadVSyncThisRow = false;
401
+ this.dispEnableSet(SCANLINEDISPENABLE);
402
+ this.cursorOn = false;
403
+ this.cursorOff = false;
404
+ }
405
+
406
+ endOfScanline() {
407
+ // End of scanline is the most complicated and quirky area of the
408
+ // 6845. A lot of different states and outcomes are possible.
409
+ // From the start of the frame, we traverse various states
410
+ // linearly, with most optional:
411
+ // - Normal rendering.
412
+ // - Last scanline of normal rendering (vertical adjust pending).
413
+ // - Vertical adjust.
414
+ // - Last scanline of vertical adjust (dummy raster pending).
415
+ // - Dummy raster. (This is for interlace timing.)
416
+ this.firstScanline = false;
417
+
418
+ if (this.scanlineCounter === this.regs[11]) this.cursorOff = true;
419
+
420
+ this.vpulseCounter = (this.vpulseCounter + 1) & 0x0f;
421
+
422
+ // Pre-counter increment compares and logic.
423
+ const r9Hit = this.scanlineCounter === this.regs[9];
424
+ if (r9Hit) {
425
+ // An R9 hit always loads a new character row address, even if
426
+ // we're in vertical adjust!
427
+ // Note that an R9 hit inside vertical adjust does not further
428
+ // increment the vertical counter, but entry into vertical
429
+ // adjust does.
430
+ this.lineStartAddr = this.nextLineStartAddr;
431
+ }
432
+
433
+ // Increment scanline.
434
+ if (this.interlacedSyncAndVideo) {
435
+ this.scanlineCounter = (this.scanlineCounter + 2) & 0x1e;
436
+ } else {
437
+ this.scanlineCounter = (this.scanlineCounter + 1) & 0x1f;
438
+ }
439
+ if (!this.teletextMode) {
440
+ // Scanlines 8-15 are off but they display again at 16,
441
+ // mirroring 0-7, and it repeats.
442
+ const off = (this.scanlineCounter >>> 3) & 1;
443
+ if (off) {
444
+ this.dispEnableClear(SCANLINEDISPENABLE);
445
+ } else {
446
+ this.dispEnableSet(SCANLINEDISPENABLE);
447
+ }
448
+ }
449
+
450
+ // Reset scanline if necessary.
451
+ if (!this.inVertAdjust && r9Hit) {
452
+ this.endOfCharacterLine();
453
+ }
454
+
455
+ if (this.endOfMainLatched && !this.endOfVertAdjustLatched) {
456
+ this.inVertAdjust = true;
457
+ }
458
+
459
+ let endOfFrame = false;
460
+
461
+ if (this.endOfFrameLatched) {
462
+ endOfFrame = true;
463
+ }
464
+
465
+ if (this.endOfVertAdjustLatched) {
466
+ this.inVertAdjust = false;
467
+ // The "dummy raster" is inserted at the very end of frame,
468
+ // after vertical adjust, for even interlace frames.
469
+ // Testing indicates interlace is checked here, a clock before
470
+ // it is entered or not.
471
+ // Like vertical adjust, C4=R4+1.
472
+ if (!!(this.regs[8] & 1) && this.doEvenFrameLogic) {
473
+ this.inDummyRaster = true;
474
+ this.endOfFrameLatched = true;
475
+ } else {
476
+ endOfFrame = true;
477
+ }
478
+ }
479
+
480
+ if (endOfFrame) {
481
+ this.endOfMainLatched = false;
482
+ this.endOfVertAdjustLatched = false;
483
+ this.endOfFrameLatched = false;
484
+ this.inDummyRaster = false;
485
+
486
+ this.endOfCharacterLine();
487
+ this.endOfFrame();
488
+ }
489
+
490
+ this.addr = this.lineStartAddr;
491
+
492
+ const cursorStartLine = this.regs[10] & 0x1f;
493
+ if (this.scanlineCounter === cursorStartLine) this.cursorOn = true;
494
+
495
+ // The teletext SAA5050 chip has its CRS pin connected to RA0, so
496
+ // we need to update it.
497
+ // The external RA0 value is modified in "interlace sync and video"
498
+ // mode to be odd for odd interlace frames.
499
+ let externalScanline = this.scanlineCounter;
500
+ if (this.interlacedSyncAndVideo && this.frameCount & 1) {
501
+ externalScanline++;
502
+ }
503
+ this.teletext.setRA0(!!(externalScanline & 1));
504
+ }
505
+
506
+ handleHSync() {
507
+ this.hpulseCounter = (this.hpulseCounter + 1) & 0x0f;
508
+ if (this.hpulseCounter === this.hpulseWidth >>> 1) {
509
+ // Start at -8 because the +8 is added before the pixel render.
510
+ this.bitmapX = -8;
511
+
512
+ // Half-clock horizontal movement
513
+ if (this.hpulseWidth & 1) {
514
+ this.bitmapX -= 4;
515
+ }
516
+
517
+ // The CRT vertical beam speed is constant, so this is actually
518
+ // an approximation that works if hsyncs are spaced evenly.
519
+ this.bitmapY += 2;
520
+
521
+ // If no VSync occurs this frame, go back to the top and force a repaint
522
+ if (this.bitmapY >= 768) {
523
+ // Arbitrary moment when TV will give up and start flyback in the absence of an explicit VSync signal
524
+ this.paintAndClear();
525
+ }
526
+ } else if (this.hpulseCounter === (this.regs[3] & 0x0f)) {
527
+ this.inHSync = false;
528
+ }
529
+ }
530
+
531
+ cb2changed(level, output) {
532
+ // Even with no light pen physically attached, the system VIA can
533
+ // configure CB2 as an output and make the CRTC think it sees a
534
+ // real light pen pulse.
535
+ // Triggers on the low -> high CB2 edge.
536
+ // Needed by Pharaoh's Curse to start.
537
+ if (level && output) {
538
+ this.regs[16] = (this.addr >> 8) & 0x3f;
539
+ this.regs[17] = this.addr & 0xff;
540
+ }
541
+ }
542
+
543
+ dispEnableChanged() {
544
+ // The DISPTMG output pin is wired to the SAA5050 teletext chip,
545
+ // for scanline tracking, so keep it apprised.
546
+ const mask = HDISPENABLE | VDISPENABLE | USERDISPENABLE;
547
+ const disptmg = (this.dispEnabled & mask) === mask;
548
+ this.teletext.setDISPTMG(disptmg);
549
+ }
550
+
551
+ dispEnableSet(flag) {
552
+ this.dispEnabled |= flag;
553
+ this.dispEnableChanged();
554
+ }
555
+
556
+ dispEnableClear(flag) {
557
+ this.dispEnabled &= ~flag;
558
+ this.dispEnableChanged();
559
+ }
560
+
561
+ ////////////////////
562
+ // Main drawing routine
563
+ polltime(clocks) {
564
+ while (clocks--) {
565
+ this.oddClock = !this.oddClock;
566
+ // Advance CRT beam.
567
+ this.bitmapX += 8;
568
+
569
+ if (this.halfClock && !this.oddClock) {
570
+ continue;
571
+ }
572
+
573
+ // This emulates the Hitachi 6845SP CRTC.
574
+ // Other variants have different quirks.
575
+ // Handle HSync
576
+ if (this.inHSync) this.handleHSync();
577
+
578
+ // Handle delayed display enable due to skew
579
+ const displayEnablePos = this.displayEnableSkew + (this.teletextMode ? 2 : 0);
580
+ if (this.horizCounter === displayEnablePos) {
581
+ this.dispEnableSet(SKEWDISPENABLE);
582
+ }
583
+
584
+ // Latch next line screen address in case we are in the last line of a character row
585
+ if (this.horizCounter === this.regs[1]) this.nextLineStartAddr = this.addr;
586
+
587
+ // Handle end of horizontal displayed.
588
+ // Make sure to account for display enable skew.
589
+ // Also, the last scanline character never displays.
590
+ if (
591
+ this.horizCounter === this.regs[1] + displayEnablePos ||
592
+ this.horizCounter === this.regs[0] + displayEnablePos
593
+ ) {
594
+ this.dispEnableClear(HDISPENABLE | SKEWDISPENABLE);
595
+ }
596
+
597
+ // Initiate HSync.
598
+ if (this.horizCounter === this.regs[2] && !this.inHSync) {
599
+ this.inHSync = true;
600
+ this.hpulseCounter = 0;
601
+ }
602
+
603
+ // Handle VSync.
604
+ // Half-line interlace timing is shown nicely in figure 13 here:
605
+ // http://bitsavers.trailing-edge.com/components/motorola/_dataSheets/6845.pdf
606
+ // Essentially, on even frames, vsync raise / lower triggers at
607
+ // the mid-scanline, and then a dummy scanline is also added
608
+ // at the end of vertical adjust.
609
+ // Without interlace, frames are 312 scanlines. With interlace,
610
+ // both odd and even frames are 312.5 scanlines.
611
+ const isInterlace = !!(this.regs[8] & 1);
612
+ // TODO: is this off-by-one? b2 uses regs[0]+1.
613
+ // TODO: does this only hit at the half-scanline or is it a
614
+ // half-scanline counter that starts when an R7 hit is noticed?
615
+ const halfR0Hit = this.horizCounter === this.regs[0] >>> 1;
616
+ const isVsyncPoint = !isInterlace || !this.doEvenFrameLogic || halfR0Hit;
617
+ let vSyncEnding = false;
618
+ let vSyncStarting = false;
619
+ if (this.inVSync && this.vpulseCounter === this.vpulseWidth && isVsyncPoint) {
620
+ vSyncEnding = true;
621
+ this.inVSync = false;
622
+ }
623
+ if (this.vertCounter === this.regs[7] && !this.inVSync && !this.hadVSyncThisRow && isVsyncPoint) {
624
+ vSyncStarting = true;
625
+ this.inVSync = true;
626
+ }
627
+
628
+ // A vsync will initiate at any character and scanline position,
629
+ // provided there isn't one in progress and provided there
630
+ // wasn't already one in this character row.
631
+ // This is an interesting finding, on a real model B.
632
+ // One further emulated quirk is that in the corner case of a
633
+ // vsync ending and starting at the same time, the vsync
634
+ // pulse continues uninterrupted. The vsync pulse counter will
635
+ // continue counting up and wrap at 16.
636
+ if (vSyncStarting && !vSyncEnding) {
637
+ this.hadVSyncThisRow = true;
638
+ this.vpulseCounter = 0;
639
+
640
+ // Avoid intense painting if registers have boot-up or
641
+ // otherwise small values.
642
+ if (this.regs[0] && this.regs[4]) {
643
+ this.paintAndClear();
644
+ }
645
+ }
646
+
647
+ if (vSyncStarting || vSyncEnding) {
648
+ this.sysvia.setVBlankInt(this.inVSync);
649
+ this.teletext.setDEW(this.inVSync);
650
+ }
651
+
652
+ // TODO: this will be cleaner if we rework skew to have fetch
653
+ // independent from render.
654
+ const insideBorder = (this.dispEnabled & (HDISPENABLE | VDISPENABLE)) === (HDISPENABLE | VDISPENABLE);
655
+ if ((insideBorder || this.cursorDrawIndex) && this.dispEnabled & FRAMESKIPENABLE) {
656
+ // Read data from address pointer if both horizontal and vertical display enabled.
657
+ const dat = this.readVideoMem();
658
+ if (insideBorder) {
659
+ if (this.teletextMode) {
660
+ this.teletext.fetchData(dat);
661
+ }
662
+
663
+ // Check cursor start.
664
+ if (
665
+ this.addr === this.cursorPos &&
666
+ this.cursorOn &&
667
+ !this.cursorOff &&
668
+ this.horizCounter < this.regs[1]
669
+ ) {
670
+ this.cursorDrawIndex = 3 - ((this.regs[8] >>> 6) & 3);
671
+ }
672
+ }
673
+
674
+ // Render data depending on display enable state.
675
+ if (this.bitmapX >= 0 && this.bitmapX < 1024 && this.bitmapY < 625) {
676
+ let doubledLines = false;
677
+ let offset = this.bitmapY;
678
+ // There's a painting subtlety here: if we're in an
679
+ // interlace mode but R6>R4 then we'll get stuck
680
+ // painting just an odd or even frame, so we double up
681
+ // scanlines to avoid a ghost half frame.
682
+ if (
683
+ (this.doubledScanlines && !this.interlacedSyncAndVideo) ||
684
+ this.isEvenRender === this.lastRenderWasEven
685
+ ) {
686
+ doubledLines = true;
687
+ offset &= ~1;
688
+ }
689
+
690
+ offset = offset * 1024 + this.bitmapX;
691
+
692
+ if ((this.dispEnabled & EVERYTHINGENABLED) === EVERYTHINGENABLED) {
693
+ if (this.teletextMode) {
694
+ this.teletext.render(this.fb32, offset);
695
+ } else {
696
+ this.blitFb(dat, offset, this.pixelsPerChar, doubledLines);
697
+ }
698
+ if (doubledLines) {
699
+ this.fb32.copyWithin(offset + 1024, offset, offset + this.pixelsPerChar);
700
+ }
701
+ }
702
+ if (this.cursorDrawIndex) {
703
+ this.handleCursor(offset, doubledLines);
704
+ }
705
+ }
706
+ }
707
+
708
+ // CRTC MA always increments, inside display border or not.
709
+ this.addr = (this.addr + 1) & 0x3fff;
710
+
711
+ // The Hitachi 6845 decides to end (or never enter) vertical
712
+ // adjust here, one clock after checking whether to enter
713
+ // vertical adjust.
714
+ // In a normal frame, this is C0=2.
715
+ if (this.checkVertAdjust) {
716
+ this.checkVertAdjust = false;
717
+ if (this.endOfMainLatched) {
718
+ if (this.vertAdjustCounter === this.regs[5]) {
719
+ this.endOfVertAdjustLatched = true;
720
+ }
721
+ this.vertAdjustCounter++;
722
+ this.vertAdjustCounter &= 0x1f;
723
+ }
724
+ }
725
+
726
+ // The Hitachi 6845 appears to latch some form of "last scanline
727
+ // of the frame" state. As shown by Twisted Brain, changing R9
728
+ // from 0 to 6 on the last scanline of the frame does not
729
+ // prevent a new frame from starting.
730
+ // Testing indicates that the latch is set here at exactly C0=1.
731
+ // See also: http://www.cpcwiki.eu/forum/programming/crtc-detailed-operation/msg177585/
732
+ if (this.horizCounter === 1) {
733
+ if (this.vertCounter === this.regs[4] && this.scanlineCounter === this.regs[9]) {
734
+ this.endOfMainLatched = true;
735
+ this.vertAdjustCounter = 0;
736
+ }
737
+ // The very next cycle (be it on this same scanline or the
738
+ // next) is used for checking the vertical adjust counter.
739
+ this.checkVertAdjust = true;
740
+ }
741
+
742
+ // Handle horizontal total.
743
+ if (this.horizCounter === this.regs[0]) {
744
+ this.endOfScanline();
745
+ this.horizCounter = 0;
746
+ this.dispEnableSet(HDISPENABLE);
747
+ } else {
748
+ this.horizCounter = (this.horizCounter + 1) & 0xff;
749
+ }
750
+
751
+ // Handle end of vertical displayed.
752
+ // The Hitachi 6845 will notice this equality at any character,
753
+ // including in the middle of a scanline.
754
+ // An exception is the very first scanline of a frame, where
755
+ // vertical display is always on.
756
+ // We do this after the render and various counter increments
757
+ // because there seems to be a 1 character delay between setting
758
+ // R6=C4 and display actually stopping.
759
+ const r6Hit = this.vertCounter === this.regs[6];
760
+ if (r6Hit && !this.firstScanline && this.dispEnabled & VDISPENABLE) {
761
+ this.dispEnableClear(VDISPENABLE);
762
+ // Perhaps surprisingly, this happens here. Both cursor
763
+ // blink and interlace cease if R6 > R4.
764
+ this.frameCount++;
765
+ }
766
+
767
+ // Interlace quirk: an even frame appears to need to see
768
+ // either of an R6 hit or R7 hit in order to activate the
769
+ // dummy raster.
770
+ const r7Hit = this.vertCounter === this.regs[7];
771
+ if (r6Hit || r7Hit) {
772
+ this.doEvenFrameLogic = !!(this.frameCount & 1);
773
+ }
774
+ } // matches while
775
+ }
776
+ }
777
+
778
+ export class FakeVideo {
779
+ constructor() {
780
+ this.ula = this.crtc = {
781
+ read: function () {
782
+ return 0xff;
783
+ },
784
+ write: utils.noop,
785
+ };
786
+ this.regs = new Uint8Array(32);
787
+ }
788
+
789
+ reset() {}
790
+
791
+ polltime() {}
792
+
793
+ setScreenHwScroll() {}
794
+ }