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
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+
3
+ import * as utils from "./utils.js";
4
+
5
+ const PollHz = 8; // Made up
6
+ const PollCycles = (2 * 1000 * 1000) / PollHz;
7
+
8
+ function doScale(val, scale, margin) {
9
+ val = (val - margin) / (1 - 2 * margin);
10
+ return val * scale;
11
+ }
12
+
13
+ export class TouchScreen {
14
+ constructor(scheduler) {
15
+ this.scheduler = scheduler;
16
+ this.mouse = [];
17
+ this.outBuffer = new utils.Fifo(16);
18
+ this.delay = 0;
19
+ this.mode = 0;
20
+ this.pollTask = this.scheduler.newTask(this.poll);
21
+ }
22
+
23
+ tryReceive(rts) {
24
+ if (this.outBuffer.size && rts) return this.outBuffer.get();
25
+ return -1;
26
+ }
27
+
28
+ doRead() {
29
+ const scaleX = 120,
30
+ marginX = 0.13;
31
+ const scaleY = 100,
32
+ marginY = 0.03;
33
+ const scaledX = doScale(this.mouse.x, scaleX, marginX);
34
+ const scaledY = doScale(1 - this.mouse.y, scaleY, marginY);
35
+ const toSend = [0x4f, 0x4f, 0x4f, 0x4f];
36
+ const x = Math.min(255, Math.max(0, scaledX)) | 0;
37
+ const y = Math.min(255, Math.max(0, scaledY)) | 0;
38
+ if (this.mouse.button) {
39
+ toSend[0] = 0x40 | ((x & 0xf0) >>> 4);
40
+ toSend[1] = 0x40 | (x & 0x0f);
41
+ toSend[2] = 0x40 | ((y & 0xf0) >>> 4);
42
+ toSend[3] = 0x40 | (y & 0x0f);
43
+ }
44
+ for (let i = 0; i < 4; ++i) this.store(toSend[i]);
45
+ this.store(".".charCodeAt(0));
46
+ }
47
+
48
+ poll() {
49
+ this.doRead();
50
+ this.pollTask.reschedule(PollCycles);
51
+ }
52
+
53
+ store(byte) {
54
+ this.outBuffer.put(byte);
55
+ }
56
+
57
+ onMouse(x, y, button) {
58
+ this.mouse = { x: x, y: y, button: button };
59
+ }
60
+
61
+ onTransmit(val) {
62
+ switch (String.fromCharCode(val)) {
63
+ case "M":
64
+ this.mode = 0;
65
+ break;
66
+ case "0":
67
+ case "1":
68
+ case "2":
69
+ case "3":
70
+ case "4":
71
+ case "5":
72
+ case "6":
73
+ case "7":
74
+ case "8":
75
+ case "9":
76
+ this.mode = 10 * this.mode + val - "0".charCodeAt(0);
77
+ break;
78
+ case ".":
79
+ break;
80
+ case "?":
81
+ if (this.mode === 1) this.doRead();
82
+ break;
83
+ }
84
+ this.pollTask.ensureScheduled(this.mode === 129 || this.mode === 130, PollCycles);
85
+ }
86
+ }
package/src/tube.js ADDED
@@ -0,0 +1,349 @@
1
+ "use strict";
2
+ import * as utils from "./utils.js";
3
+
4
+ // this one should be declared more globally
5
+ const HOST_CPU_FLAG_IRQ_TUBE_ULA = 8;
6
+
7
+ const TUBE_ULA_R1 = 0;
8
+ const TUBE_ULA_R2 = 1;
9
+ const TUBE_ULA_R3 = 2;
10
+ const TUBE_ULA_R4 = 3;
11
+ const TUBE_ULA_R1_STATUS_ADDRESS = 0;
12
+ const TUBE_ULA_R1_DATA_ADDRESS = 1;
13
+ const TUBE_ULA_R2_STATUS_ADDRESS = 2;
14
+ const TUBE_ULA_R2_DATA_ADDRESS = 3;
15
+ const TUBE_ULA_R3_STATUS_ADDRESS = 4;
16
+ const TUBE_ULA_R3_DATA_ADDRESS = 5;
17
+ const TUBE_ULA_R4_STATUS_ADDRESS = 6;
18
+ const TUBE_ULA_R4_DATA_ADDRESS = 7;
19
+ const TUBE_ULA_FLAG_DATA_AVAILABLE = 0x80;
20
+ const TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL = 0x40;
21
+ const TUBE_ULA_FLAG_STATUS_Q = 0x01;
22
+ const TUBE_ULA_FLAG_STATUS_I = 0x02;
23
+ const TUBE_ULA_FLAG_STATUS_J = 0x04;
24
+ const TUBE_ULA_FLAG_STATUS_M = 0x08;
25
+ const TUBE_ULA_FLAG_STATUS_V = 0x10;
26
+ const TUBE_ULA_FLAG_STATUS_P = 0x20;
27
+ const TUBE_ULA_FLAG_STATUS_T = 0x40;
28
+ const TUBE_ULA_FLAG_STATUS_S = 0x80;
29
+ // human-readable aliases for the above flags
30
+ const TUBE_ULA_FLAG_STATUS_ENABLE_HOST_IRQ_FROM_R4_DATA = TUBE_ULA_FLAG_STATUS_Q;
31
+ const TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_IRQ_FROM_R1_DATA = TUBE_ULA_FLAG_STATUS_I;
32
+ const TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_IRQ_FROM_R4_DATA = TUBE_ULA_FLAG_STATUS_J;
33
+ const TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_NMI_FROM_R3_DATA = TUBE_ULA_FLAG_STATUS_M;
34
+ const TUBE_ULA_FLAG_STATUS_ENABLE_2_BYTE_R3_DATA = TUBE_ULA_FLAG_STATUS_V;
35
+ const TUBE_ULA_FLAG_STATUS_PARASITE_RESET_ACTIVE_LOW = TUBE_ULA_FLAG_STATUS_P;
36
+ const TUBE_ULA_FLAG_STATUS_CLEAR_ALL_TUBE_REGISTERS = TUBE_ULA_FLAG_STATUS_T;
37
+ const TUBE_ULA_FLAG_STATUS_SET_CONTROL_FLAGS = TUBE_ULA_FLAG_STATUS_S;
38
+ const TUBE_ULA_R1_PARASITE_BYTE_COUNT = 24;
39
+
40
+ export class Tube {
41
+ constructor(hostCpu, parasiteCpu) {
42
+ this.hostCpu = hostCpu;
43
+ this.parasiteCpu = parasiteCpu;
44
+ this.internalStatusRegister = 0;
45
+ this.hostStatus = new Uint8Array(4);
46
+ this.parasiteStatus = new Uint8Array(4);
47
+ this.parasiteToHostData = [
48
+ new Uint8Array(TUBE_ULA_R1_PARASITE_BYTE_COUNT),
49
+ new Uint8Array(1),
50
+ new Uint8Array(2),
51
+ new Uint8Array(1),
52
+ ];
53
+ this.hostToParasiteData = [new Uint8Array(1), new Uint8Array(1), new Uint8Array(2), new Uint8Array(1)];
54
+ this.parasiteToHostFifoByteCount1 = 0;
55
+ this.parasiteToHostFifoByteCount3 = 0;
56
+ this.hostToParasiteFifoByteCount3 = 0;
57
+ this.debug = false;
58
+ }
59
+ reset(updateInternalStatusRegister = true) {
60
+ if (updateInternalStatusRegister) {
61
+ this.internalStatusRegister = 0;
62
+ }
63
+ for (let i = 0; i < 4; i++) {
64
+ this.hostStatus[i] = TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
65
+ this.parasiteStatus[i] = TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
66
+ if (i === TUBE_ULA_R3) {
67
+ // register 3 has one valid but insignificant byte in the parasite to host FIFO (this is to prevent an immediate PNMI state after PRST)
68
+ this.hostStatus[i] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
69
+ this.parasiteToHostData[i][0] = 0;
70
+ }
71
+ }
72
+ this.parasiteToHostFifoByteCount1 = 0;
73
+ // see info in the loop above from Tube Application Note about R3
74
+ this.parasiteToHostFifoByteCount3 = 1;
75
+ this.hostToParasiteFifoByteCount3 = 0;
76
+ this.updateInterrupts();
77
+ }
78
+ updateInterrupts() {
79
+ // host IRQ
80
+ if (
81
+ this.hostStatus[TUBE_ULA_R4] & TUBE_ULA_FLAG_DATA_AVAILABLE &&
82
+ this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_HOST_IRQ_FROM_R4_DATA
83
+ ) {
84
+ this.hostCpu.interrupt |= HOST_CPU_FLAG_IRQ_TUBE_ULA;
85
+ } else {
86
+ this.hostCpu.interrupt &= ~HOST_CPU_FLAG_IRQ_TUBE_ULA;
87
+ }
88
+ // parasite IRQ
89
+ if (
90
+ (this.parasiteStatus[TUBE_ULA_R1] & TUBE_ULA_FLAG_DATA_AVAILABLE &&
91
+ this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_IRQ_FROM_R1_DATA) ||
92
+ (this.parasiteStatus[TUBE_ULA_R4] & TUBE_ULA_FLAG_DATA_AVAILABLE &&
93
+ this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_IRQ_FROM_R4_DATA)
94
+ ) {
95
+ this.parasiteCpu.interrupt = true;
96
+ } else {
97
+ this.parasiteCpu.interrupt = false;
98
+ }
99
+ // parasite NMI
100
+ // (from Tube Application Note)
101
+ // either: M = 1, V = 0, 1 or 2 bytes in host to parasite register 3 FIFO or 0 bytes in parasite
102
+ // to host register 3 FIFO (this allows single byte transfers across
103
+ // register 3)
104
+ // or: M = 1, V = 1, 2 bytes in host to parasite register 3 FIFO or 0 bytes in parasite to host
105
+ // register 3 FIFO. (this allows two byte transfers across register 3)
106
+ const r3Size = this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_2_BYTE_R3_DATA ? 2 : 1;
107
+ if (
108
+ this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_NMI_FROM_R3_DATA &&
109
+ (this.hostToParasiteFifoByteCount3 >= r3Size || this.parasiteToHostFifoByteCount3 === 0)
110
+ ) {
111
+ this.parasiteCpu.NMI(true);
112
+ } else {
113
+ this.parasiteCpu.NMI(false);
114
+ }
115
+ // parasite CPU RESET held low - not implemented in the CPU - the CPU should be frozen until this signal is released
116
+ this.parasiteCpu.resetHeldLow = this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_PARASITE_RESET_ACTIVE_LOW;
117
+ }
118
+ hostRead(address) {
119
+ let result = 0xfe;
120
+ switch (address & 7) {
121
+ case TUBE_ULA_R1_STATUS_ADDRESS:
122
+ result =
123
+ (this.hostStatus[TUBE_ULA_R1] &
124
+ (TUBE_ULA_FLAG_DATA_AVAILABLE | TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL)) |
125
+ (this.internalStatusRegister &
126
+ ~(TUBE_ULA_FLAG_DATA_AVAILABLE | TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL));
127
+ break;
128
+ case TUBE_ULA_R1_DATA_ADDRESS:
129
+ result = this.parasiteToHostData[TUBE_ULA_R1][0];
130
+ if (this.hostStatus[TUBE_ULA_R1] & TUBE_ULA_FLAG_DATA_AVAILABLE) {
131
+ for (let i = 1; i < TUBE_ULA_R1_PARASITE_BYTE_COUNT; i++) {
132
+ this.parasiteToHostData[TUBE_ULA_R1][i - 1] = this.parasiteToHostData[TUBE_ULA_R1][i];
133
+ }
134
+ this.parasiteStatus[TUBE_ULA_R1] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
135
+ this.parasiteToHostFifoByteCount1--;
136
+ if (this.parasiteToHostFifoByteCount1 === 0) {
137
+ this.hostStatus[TUBE_ULA_R1] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
138
+ }
139
+ }
140
+ break;
141
+ case TUBE_ULA_R2_STATUS_ADDRESS:
142
+ result = this.hostStatus[TUBE_ULA_R2];
143
+ break;
144
+ case TUBE_ULA_R2_DATA_ADDRESS:
145
+ result = this.parasiteToHostData[TUBE_ULA_R2][0];
146
+ this.parasiteStatus[TUBE_ULA_R2] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
147
+ this.hostStatus[TUBE_ULA_R2] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
148
+ break;
149
+ case TUBE_ULA_R3_STATUS_ADDRESS:
150
+ result = this.hostStatus[TUBE_ULA_R3];
151
+ break;
152
+ case TUBE_ULA_R3_DATA_ADDRESS:
153
+ result = this.parasiteToHostData[TUBE_ULA_R3][0];
154
+ if (this.hostStatus[TUBE_ULA_R3] & TUBE_ULA_FLAG_DATA_AVAILABLE) {
155
+ this.parasiteToHostData[TUBE_ULA_R3][0] = this.parasiteToHostData[TUBE_ULA_R3][1];
156
+ this.parasiteStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
157
+ this.parasiteToHostFifoByteCount3--;
158
+ if (this.parasiteToHostFifoByteCount3 === 0) {
159
+ this.hostStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
160
+ }
161
+ }
162
+ break;
163
+ case TUBE_ULA_R4_STATUS_ADDRESS:
164
+ result = this.hostStatus[TUBE_ULA_R4];
165
+ break;
166
+ case TUBE_ULA_R4_DATA_ADDRESS:
167
+ result = this.parasiteToHostData[TUBE_ULA_R4][0];
168
+ this.parasiteStatus[TUBE_ULA_R4] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
169
+ this.hostStatus[TUBE_ULA_R4] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
170
+ break;
171
+ }
172
+ this.updateInterrupts();
173
+ if (this.debug) {
174
+ console.log("TUBE ULA: host read " + utils.hexword(address) + " = " + utils.hexbyte(result));
175
+ }
176
+ return result;
177
+ }
178
+ hostWrite(address, value) {
179
+ if (this.debug) {
180
+ console.log("TUBE ULA: host write " + utils.hexword(address) + " = " + utils.hexbyte(value));
181
+ }
182
+ switch (address & 7) {
183
+ case TUBE_ULA_R1_STATUS_ADDRESS:
184
+ if (value & TUBE_ULA_FLAG_STATUS_SET_CONTROL_FLAGS) {
185
+ this.internalStatusRegister |=
186
+ value & ~(TUBE_ULA_FLAG_DATA_AVAILABLE | TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL);
187
+ } else {
188
+ this.internalStatusRegister &= ~(
189
+ value & ~(TUBE_ULA_FLAG_DATA_AVAILABLE | TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL)
190
+ );
191
+ }
192
+ if (value & TUBE_ULA_FLAG_STATUS_CLEAR_ALL_TUBE_REGISTERS) {
193
+ this.reset(false);
194
+ }
195
+ if (value & TUBE_ULA_FLAG_STATUS_PARASITE_RESET_ACTIVE_LOW) {
196
+ // there is still an issue with the parasite OS that runs after this happens
197
+ // it prints the startup banner but then seems to stop responding when a R3 data
198
+ // transfer (based on Advanced User Guide example) is attempted
199
+ if (this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_PARASITE_RESET_ACTIVE_LOW) {
200
+ this.parasiteCpu.reset(true); // this in turn calls our this.reset(true)
201
+ }
202
+ }
203
+ break;
204
+ case TUBE_ULA_R1_DATA_ADDRESS:
205
+ if (this.hostStatus[TUBE_ULA_R1] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
206
+ this.hostToParasiteData[TUBE_ULA_R1][0] = value;
207
+ this.parasiteStatus[TUBE_ULA_R1] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
208
+ this.hostStatus[TUBE_ULA_R1] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
209
+ }
210
+ break;
211
+ case TUBE_ULA_R2_DATA_ADDRESS:
212
+ if (this.hostStatus[TUBE_ULA_R2] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
213
+ this.hostToParasiteData[TUBE_ULA_R2][0] = value;
214
+ this.parasiteStatus[TUBE_ULA_R2] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
215
+ this.hostStatus[TUBE_ULA_R2] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
216
+ }
217
+ break;
218
+ case TUBE_ULA_R3_DATA_ADDRESS:
219
+ if (this.hostStatus[TUBE_ULA_R3] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
220
+ if (this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_2_BYTE_R3_DATA) {
221
+ if (this.hostToParasiteFifoByteCount3 < 2) {
222
+ this.hostToParasiteData[this.hostToParasiteFifoByteCount3++] = value;
223
+ }
224
+ if (this.hostToParasiteFifoByteCount3 === 2) {
225
+ this.parasiteStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
226
+ this.hostStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
227
+ }
228
+ } else {
229
+ this.hostToParasiteData[TUBE_ULA_R3][0] = value;
230
+ this.hostToParasiteFifoByteCount3 = 1;
231
+ this.parasiteStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
232
+ this.hostStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
233
+ }
234
+ }
235
+ break;
236
+ case TUBE_ULA_R4_DATA_ADDRESS:
237
+ if (this.hostStatus[TUBE_ULA_R4] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
238
+ this.hostToParasiteData[TUBE_ULA_R4][0] = value;
239
+ this.parasiteStatus[TUBE_ULA_R4] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
240
+ this.hostStatus[TUBE_ULA_R4] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
241
+ }
242
+ break;
243
+ }
244
+ this.updateInterrupts();
245
+ }
246
+ parasiteRead(address) {
247
+ // Not implemented - needs to be integrated with the parasite CPU code:
248
+ // Boot mode is terminated by the software when it selects any one of the Tube addresses.
249
+ // This deselects the ROM
250
+ let result = 0;
251
+ switch (address & 7) {
252
+ case TUBE_ULA_R1_STATUS_ADDRESS:
253
+ result = this.parasiteStatus[TUBE_ULA_R1];
254
+ break;
255
+ case TUBE_ULA_R1_DATA_ADDRESS:
256
+ result = this.hostToParasiteData[TUBE_ULA_R1][0];
257
+ this.hostStatus[TUBE_ULA_R1] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
258
+ this.parasiteStatus[TUBE_ULA_R1] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
259
+ break;
260
+ case TUBE_ULA_R2_STATUS_ADDRESS:
261
+ result = this.parasiteStatus[TUBE_ULA_R2];
262
+ break;
263
+ case TUBE_ULA_R2_DATA_ADDRESS:
264
+ result = this.hostToParasiteData[TUBE_ULA_R2][0];
265
+ this.hostStatus[TUBE_ULA_R2] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
266
+ this.parasiteStatus[TUBE_ULA_R2] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
267
+ break;
268
+ case TUBE_ULA_R3_STATUS_ADDRESS:
269
+ result = this.parasiteStatus[TUBE_ULA_R3];
270
+ break;
271
+ case TUBE_ULA_R3_DATA_ADDRESS:
272
+ result = this.hostToParasiteData[TUBE_ULA_R3][0];
273
+ if (this.parasiteStatus[TUBE_ULA_R3] & TUBE_ULA_FLAG_DATA_AVAILABLE) {
274
+ this.hostToParasiteData[TUBE_ULA_R3][0] = this.hostToParasiteData[TUBE_ULA_R3][1];
275
+ this.hostToParasiteFifoByteCount3--;
276
+ if (this.hostToParasiteFifoByteCount3 === 0) {
277
+ this.hostStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
278
+ this.parasiteStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
279
+ }
280
+ }
281
+ break;
282
+ case TUBE_ULA_R4_STATUS_ADDRESS:
283
+ result = this.parasiteStatus[TUBE_ULA_R4];
284
+ break;
285
+ case TUBE_ULA_R4_DATA_ADDRESS:
286
+ result = this.hostToParasiteData[TUBE_ULA_R4][0];
287
+ this.hostStatus[TUBE_ULA_R4] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
288
+ this.parasiteStatus[TUBE_ULA_R4] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
289
+ break;
290
+ }
291
+ this.updateInterrupts();
292
+ if (this.debug) {
293
+ console.log("TUBE ULA: parasite read " + utils.hexword(address) + " = " + utils.hexbyte(result));
294
+ }
295
+ return result;
296
+ }
297
+ parasiteWrite(address, value) {
298
+ // Not implemented - needs to be integrated with the parasite CPU code:
299
+ // Boot mode is terminated by the software when it selects any one of the Tube addresses.
300
+ // This deselects the ROM
301
+ if (this.debug) {
302
+ console.log("TUBE ULA: parasite write " + utils.hexword(address) + " = " + utils.hexbyte(value));
303
+ }
304
+ switch (address & 7) {
305
+ case TUBE_ULA_R1_DATA_ADDRESS:
306
+ if (this.parasiteStatus[TUBE_ULA_R1] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
307
+ this.parasiteToHostData[TUBE_ULA_R1][this.parasiteToHostFifoByteCount1++] = value;
308
+ this.hostStatus[TUBE_ULA_R1] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
309
+ if (this.parasiteToHostFifoByteCount1 === TUBE_ULA_R1_PARASITE_BYTE_COUNT) {
310
+ this.parasiteStatus[TUBE_ULA_R1] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
311
+ }
312
+ }
313
+ break;
314
+ case TUBE_ULA_R2_DATA_ADDRESS:
315
+ if (this.parasiteStatus[TUBE_ULA_R2] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
316
+ this.parasiteToHostData[TUBE_ULA_R2][0] = value;
317
+ this.hostStatus[TUBE_ULA_R2] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
318
+ this.parasiteStatus[TUBE_ULA_R2] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
319
+ }
320
+ break;
321
+ case TUBE_ULA_R3_DATA_ADDRESS:
322
+ if (this.parasiteStatus[TUBE_ULA_R3] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
323
+ if (this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_2_BYTE_R3_DATA) {
324
+ if (this.parasiteToHostFifoByteCount3 < 2) {
325
+ this.parasiteToHostData[TUBE_ULA_R3][this.parasiteToHostFifoByteCount3++] = value;
326
+ }
327
+ if (this.parasiteToHostFifoByteCount3 === 2) {
328
+ this.hostStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
329
+ this.parasiteStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
330
+ }
331
+ } else {
332
+ this.parasiteToHostData[TUBE_ULA_R3][0] = value;
333
+ this.parasiteToHostFifoByteCount3 = 1;
334
+ this.hostStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
335
+ this.parasiteStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
336
+ }
337
+ }
338
+ break;
339
+ case TUBE_ULA_R4_DATA_ADDRESS:
340
+ if (this.parasiteStatus[TUBE_ULA_R4] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
341
+ this.parasiteToHostData[TUBE_ULA_R4][0] = value;
342
+ this.hostStatus[TUBE_ULA_R4] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
343
+ this.parasiteStatus[TUBE_ULA_R4] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
344
+ }
345
+ break;
346
+ }
347
+ this.updateInterrupts();
348
+ }
349
+ }
@@ -0,0 +1,256 @@
1
+ /**
2
+ * URL parameter handling for jsbeeb
3
+ */
4
+
5
+ /**
6
+ * Check if a value is defined (not null and not undefined)
7
+ * @param {*} value - The value to check
8
+ * @returns {boolean} True if the value is neither null nor undefined
9
+ */
10
+ function isDefined(value) {
11
+ return value !== null && value !== undefined;
12
+ }
13
+
14
+ /**
15
+ * @typedef {"string"|"array"|"int"|"float"|"bool"} ParamType
16
+ */
17
+
18
+ /**
19
+ * Parameter type enum to avoid string literals
20
+ * @enum {string}
21
+ */
22
+ export const ParamTypes = {
23
+ /** String parameter (default) */
24
+ STRING: "string",
25
+ /** Array parameter (for parameters that can appear multiple times) */
26
+ ARRAY: "array",
27
+ /** Integer parameter */
28
+ INT: "int",
29
+ /** Float parameter */
30
+ FLOAT: "float",
31
+ /** Boolean parameter (true if present, regardless of value) */
32
+ BOOL: "bool",
33
+ };
34
+
35
+ /**
36
+ * Parse a query string into an object
37
+ * @param {string} queryString - The query string to parse
38
+ * @param {Object.<string, ParamType>} [paramTypes={}] - A map of parameter names to their types
39
+ * @returns {Object} Object containing parsed query parameters
40
+ */
41
+ export function parseQueryString(queryString, paramTypes = {}) {
42
+ if (!queryString) return {};
43
+
44
+ // workaround for shonky python web server
45
+ const cleanQueryString = queryString.endsWith("/") ? queryString.substring(0, queryString.length - 1) : queryString;
46
+
47
+ const parsedQuery = {};
48
+
49
+ cleanQueryString.split("&").forEach(function (keyval) {
50
+ if (!keyval) return;
51
+
52
+ const keyAndVal = keyval.split("=");
53
+ const key = decodeURIComponent(keyAndVal[0]);
54
+ let val = null;
55
+ if (keyAndVal.length > 1) val = decodeURIComponent(keyAndVal[1]);
56
+
57
+ const paramType = paramTypes[key] || ParamTypes.STRING;
58
+
59
+ switch (paramType) {
60
+ case ParamTypes.ARRAY:
61
+ if (!parsedQuery[key]) {
62
+ parsedQuery[key] = [];
63
+ }
64
+ parsedQuery[key].push(val);
65
+ break;
66
+ case ParamTypes.INT:
67
+ if (val !== undefined) {
68
+ const parsed = parseInt(val, 10);
69
+ parsedQuery[key] = isNaN(parsed) ? 0 : parsed;
70
+ }
71
+ break;
72
+ case ParamTypes.FLOAT:
73
+ if (val !== undefined) {
74
+ const parsed = parseFloat(val);
75
+ parsedQuery[key] = isNaN(parsed) ? 0 : parsed;
76
+ }
77
+ break;
78
+ case ParamTypes.BOOL:
79
+ // Only the exact 'false' string is treated as false.
80
+ parsedQuery[key] = val !== "false";
81
+ break;
82
+ case ParamTypes.STRING:
83
+ default:
84
+ parsedQuery[key] = val;
85
+ break;
86
+ }
87
+ });
88
+
89
+ return parsedQuery;
90
+ }
91
+
92
+ /**
93
+ * Build a URL string from base URL and query parameters
94
+ * @param {string} baseUrl - The base URL (without query string)
95
+ * @param {Object} parsedQuery - Object containing query parameters
96
+ * @param {Object.<string, ParamType>} [paramTypes={}] - Object mapping parameter names to their types
97
+ * @returns {string} The complete URL with query parameters
98
+ */
99
+ /**
100
+ * Append a parameter to the URL
101
+ * @param {string} url - Current URL
102
+ * @param {string} sep - Current separator (? or &)
103
+ * @param {string} key - Parameter key
104
+ * @param {string} [value] - Parameter value (optional for boolean parameters)
105
+ * @returns {Object} Updated URL and separator
106
+ */
107
+ function appendParam(url, sep, key, value = undefined) {
108
+ url += sep + encodeURIComponent(key);
109
+ if (value !== undefined) {
110
+ url += "=" + encodeURIComponent(value);
111
+ }
112
+ return { url, sep: "&" };
113
+ }
114
+
115
+ export function buildUrlFromParams(baseUrl, parsedQuery, paramTypes = {}) {
116
+ let url = baseUrl;
117
+ let sep = "?";
118
+
119
+ Object.entries(parsedQuery).forEach(([key, value]) => {
120
+ if (key.length === 0) return;
121
+
122
+ // Default to STRING unless explicitly specified
123
+ const paramType = paramTypes[key] || ParamTypes.STRING;
124
+
125
+ switch (paramType) {
126
+ case ParamTypes.ARRAY:
127
+ // Handle array parameters - each item becomes a separate parameter
128
+ if (Array.isArray(value) && value.length > 0) {
129
+ value.forEach((val) => {
130
+ if (isDefined(val)) {
131
+ const result = appendParam(url, sep, key, val);
132
+ url = result.url;
133
+ sep = result.sep;
134
+ }
135
+ });
136
+ }
137
+ break;
138
+ case ParamTypes.BOOL:
139
+ // For boolean params, only add the key without value if true
140
+ if (value === true) {
141
+ const result = appendParam(url, sep, key);
142
+ url = result.url;
143
+ sep = result.sep;
144
+ }
145
+ break;
146
+ case ParamTypes.INT:
147
+ case ParamTypes.FLOAT:
148
+ case ParamTypes.STRING:
149
+ default:
150
+ // Include the parameter if it has a value (including zero)
151
+ if (isDefined(value) && value !== "") {
152
+ const result = appendParam(url, sep, key, value);
153
+ url = result.url;
154
+ sep = result.sep;
155
+ }
156
+ break;
157
+ }
158
+ });
159
+
160
+ return url;
161
+ }
162
+
163
+ /**
164
+ * Process keyboard mapping parameters from query string
165
+ * @param {Object} parsedQuery - The parsed query parameters
166
+ * @param {Object} BBC - BBC key constants
167
+ * @param {Object} keyCodes - Key code constants
168
+ * @param {Array} userKeymap - Array to store user key mappings
169
+ * @param {Object} gamepad - Gamepad object for handling mapping
170
+ * @returns {Object} Updated query parameters
171
+ */
172
+ export function processKeyboardParams(parsedQuery, BBC, keyCodes, userKeymap, gamepad) {
173
+ Object.entries(parsedQuery).forEach(([key, val]) => {
174
+ if (!val) return;
175
+
176
+ // eg KEY.CAPSLOCK=CTRL
177
+ if (key.toUpperCase().indexOf("KEY.") === 0) {
178
+ const bbcKey = val.toUpperCase();
179
+
180
+ if (BBC[bbcKey]) {
181
+ const nativeKey = key.substring(4).toUpperCase(); // remove KEY.
182
+ if (keyCodes[nativeKey]) {
183
+ console.log("mapping " + nativeKey + " to " + bbcKey);
184
+ userKeymap.push({ native: nativeKey, bbc: bbcKey });
185
+ } else {
186
+ console.log("unknown key: " + nativeKey);
187
+ }
188
+ } else {
189
+ console.log("unknown BBC key: " + val);
190
+ }
191
+ } else if (key.indexOf("GP.") === 0) {
192
+ // gamepad mapping
193
+ // eg ?GP.FIRE2=RETURN
194
+ const gamepadKey = key.substring(3).toUpperCase(); // remove GP. prefix
195
+ gamepad.remap(gamepadKey, val.toUpperCase());
196
+ } else {
197
+ switch (key) {
198
+ case "LEFT":
199
+ case "RIGHT":
200
+ case "UP":
201
+ case "DOWN":
202
+ case "FIRE":
203
+ gamepad.remap(key, val.toUpperCase());
204
+ break;
205
+ }
206
+ }
207
+ });
208
+
209
+ return parsedQuery;
210
+ }
211
+
212
+ /**
213
+ * Process autoboot and other emulation parameters
214
+ * @param {Object} parsedQuery - The parsed query parameters
215
+ * @returns {Object} Information about autoboot settings
216
+ */
217
+ export function processAutobootParams(parsedQuery) {
218
+ let needsAutoboot = false;
219
+ let autoType = "";
220
+
221
+ if (isDefined(parsedQuery.autoboot)) {
222
+ needsAutoboot = "boot";
223
+ } else if (isDefined(parsedQuery.autochain)) {
224
+ needsAutoboot = "chain";
225
+ } else if (isDefined(parsedQuery.autorun)) {
226
+ needsAutoboot = "run";
227
+ } else if (isDefined(parsedQuery.autotype)) {
228
+ needsAutoboot = "type";
229
+ autoType = parsedQuery.autotype;
230
+ }
231
+
232
+ return { needsAutoboot, autoType };
233
+ }
234
+
235
+ /**
236
+ * Guess the appropriate model based on the hostname
237
+ * @param {string} hostname - The hostname to check
238
+ * @returns {string} Model identifier
239
+ */
240
+ export function guessModelFromHostname(hostname) {
241
+ if (hostname.startsWith("bbc")) return "B-DFS1.2";
242
+ if (hostname.startsWith("master")) return "Master";
243
+ return "B-DFS1.2";
244
+ }
245
+
246
+ /**
247
+ * Parse disc or tape images from the query parameters
248
+ * @param {Object} parsedQuery - The query parameters
249
+ * @returns {Object} Object containing disc and tape information
250
+ */
251
+ export function parseMediaParams(parsedQuery) {
252
+ const { disc, disc1, disc2, tape } = parsedQuery;
253
+ const discImage = disc || disc1;
254
+
255
+ return { discImage, secondDiscImage: disc2, tapeImage: tape };
256
+ }