jsbeeb 1.1.1 → 1.3.2

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 (450) hide show
  1. package/package.json +25 -12
  2. package/src/6502.js +51 -41
  3. package/src/app/app.js +99 -8
  4. package/src/app/electron.js +47 -5
  5. package/src/app/preload.js +11 -1
  6. package/src/config.js +9 -2
  7. package/src/disc.js +2 -2
  8. package/src/filestore.js +1 -4
  9. package/src/gamepad-source.js +1 -1
  10. package/src/machine-session.js +396 -0
  11. package/src/main.js +36 -7
  12. package/src/music5000-worklet.js +1 -0
  13. package/src/music5000.js +1 -9
  14. package/src/sth.js +3 -1
  15. package/src/utils.js +2 -2
  16. package/src/web/audio-renderer.js +1 -1
  17. package/src/web/debug.js +2 -2
  18. package/tests/test-machine.js +82 -1
  19. package/.editorconfig +0 -15
  20. package/.git-blame-ignore-revs +0 -3
  21. package/.github/copilot-instructions.md +0 -94
  22. package/.github/workflows/claude-issue-triage.yml +0 -105
  23. package/.github/workflows/claude.yml +0 -63
  24. package/.github/workflows/release-please.yml +0 -75
  25. package/.github/workflows/test-and-deploy.yml +0 -86
  26. package/.gitmodules +0 -6
  27. package/.husky/pre-commit +0 -1
  28. package/.idea/codeStyleSettings.xml +0 -9
  29. package/.idea/codeStyles/Project.xml +0 -62
  30. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  31. package/.idea/compiler.xml +0 -22
  32. package/.idea/copyright/profiles_settings.xml +0 -3
  33. package/.idea/encodings.xml +0 -6
  34. package/.idea/inspectionProfiles/Project_Default.xml +0 -7
  35. package/.idea/jsLibraryMappings.xml +0 -6
  36. package/.idea/jsLinters/jshint.xml +0 -85
  37. package/.idea/jsLinters/jslint.xml +0 -15
  38. package/.idea/jsbeeb.iml +0 -11
  39. package/.idea/misc.xml +0 -6
  40. package/.idea/modules.xml +0 -8
  41. package/.idea/prettier.xml +0 -7
  42. package/.idea/runConfigurations/Debug.xml +0 -5
  43. package/.idea/scopes/scope_settings.xml +0 -5
  44. package/.idea/vcs.xml +0 -8
  45. package/.prettierignore +0 -4
  46. package/.prettierrc.json +0 -1
  47. package/.release-please-manifest.json +0 -3
  48. package/.vscode/launch.json +0 -14
  49. package/.vscode/settings.json +0 -6
  50. package/CHANGELOG.md +0 -32
  51. package/CLAUDE.md +0 -136
  52. package/Dockerfile +0 -22
  53. package/Makefile +0 -30
  54. package/docker/nginx-default.conf +0 -10
  55. package/docs/pal-comb-filter-research.md +0 -129
  56. package/docs/pal-simulation-design.md +0 -368
  57. package/eslint.config.js +0 -35
  58. package/index.html +0 -954
  59. package/jsconfig.json +0 -10
  60. package/public/discs/README.Irq-Timing +0 -3
  61. package/public/discs/README.bcdtest +0 -5
  62. package/public/discs/README.elite +0 -6
  63. package/public/discs/README.eng_test +0 -3
  64. package/public/discs/README.protection +0 -7
  65. package/public/favicon.ico +0 -0
  66. package/public/images/botbar.png +0 -0
  67. package/public/images/cub-monitor.png +0 -0
  68. package/public/images/jsbeeb-example.png +0 -0
  69. package/public/images/placeholder.png +0 -0
  70. package/public/images/red-off-16.png +0 -0
  71. package/public/images/red-on-16.png +0 -0
  72. package/public/images/sb/CD-left.jpg +0 -0
  73. package/public/images/sb/CD-right.jpg +0 -0
  74. package/public/images/sidebar.png +0 -0
  75. package/public/images/tv.png +0 -0
  76. package/public/images/yellow-off-16.png +0 -0
  77. package/public/images/yellow-on-16.png +0 -0
  78. package/public/jsbeeb-icon.png +0 -0
  79. package/public/robots.txt +0 -3
  80. package/public/roms/ADFS1-53.rom +0 -0
  81. package/public/roms/BASIC.ROM +0 -0
  82. package/public/roms/README +0 -4
  83. package/public/roms/a01/BASIC1.rom +0 -0
  84. package/public/roms/ample.rom +0 -0
  85. package/public/roms/ats-3.0.rom +0 -0
  86. package/public/roms/b/DFS-0.9.rom +0 -0
  87. package/public/roms/b/DFS-1.2.rom +0 -0
  88. package/public/roms/b1770/dfs1770.rom +0 -0
  89. package/public/roms/b1770/zADFS.ROM +0 -0
  90. package/public/roms/bp/dfs.rom +0 -0
  91. package/public/roms/bp/zADFS.ROM +0 -0
  92. package/public/roms/bpos.rom +0 -0
  93. package/public/roms/compact/adfs210.rom +0 -0
  94. package/public/roms/compact/basic48.rom +0 -0
  95. package/public/roms/compact/basic486.rom +0 -0
  96. package/public/roms/compact/os51.rom +0 -0
  97. package/public/roms/compact/utils.rom +0 -0
  98. package/public/roms/deos.rom +0 -0
  99. package/public/roms/master/anfs-4.25.rom +0 -0
  100. package/public/roms/master/mos.txt +0 -68819
  101. package/public/roms/master/mos3.20 +0 -0
  102. package/public/roms/os.rom +0 -0
  103. package/public/roms/os01.rom +0 -0
  104. package/public/roms/tube/6502Tube.rom +0 -0
  105. package/public/roms/tube/ARMeval_100.rom +0 -0
  106. package/public/roms/tube/BIOS.ROM +0 -0
  107. package/public/roms/tube/ReCo6502ROM_816 +0 -0
  108. package/public/roms/tube/Z80_120.rom +0 -0
  109. package/public/roms/us/USBASIC.rom +0 -0
  110. package/public/roms/us/USDNFS.rom +0 -0
  111. package/public/roms/usmos.rom +0 -0
  112. package/public/sounds/disc525/motor.wav +0 -0
  113. package/public/sounds/disc525/motoroff.wav +0 -0
  114. package/public/sounds/disc525/motoron.wav +0 -0
  115. package/public/sounds/disc525/seek.wav +0 -0
  116. package/public/sounds/disc525/seek2.wav +0 -0
  117. package/public/sounds/disc525/seek3.wav +0 -0
  118. package/public/sounds/disc525/step.wav +0 -0
  119. package/public/teletext/txt0.dat +0 -0
  120. package/public/teletext/txt1.dat +0 -0
  121. package/public/teletext/txt2.dat +0 -0
  122. package/public/teletext/txt3.dat +0 -0
  123. package/release-please-config.json +0 -13
  124. package/run-container.sh +0 -92
  125. package/tests/integration/RmwX.asm +0 -47
  126. package/tests/integration/TestInstructionsSource +0 -27
  127. package/tests/integration/TestTimingsResults +0 -27
  128. package/tests/integration/TestTimingsSource +0 -61
  129. package/tests/integration/bcd.js +0 -23
  130. package/tests/integration/dormann.js +0 -101
  131. package/tests/integration/dp111timing.js +0 -42
  132. package/tests/integration/ensure-submodules.js +0 -25
  133. package/tests/integration/nops.bas +0 -119
  134. package/tests/integration/nops.js +0 -24
  135. package/tests/integration/protection.js +0 -26
  136. package/tests/integration/rmw.js +0 -69
  137. package/tests/integration/teletext/expected_bug_469.png +0 -0
  138. package/tests/integration/teletext/expected_flash_0.png +0 -0
  139. package/tests/integration/teletext/expected_flash_1.png +0 -0
  140. package/tests/integration/teletext/expected_hoglet_held_char.png +0 -0
  141. package/tests/integration/teletext/expected_reveal_flash_0.png +0 -0
  142. package/tests/integration/teletext/expected_reveal_flash_1.png +0 -0
  143. package/tests/integration/teletext.js +0 -126
  144. package/tests/integration/timings.js +0 -56
  145. package/tests/integration/via.js +0 -1125
  146. package/tests/suite/README.md +0 -7
  147. package/tests/suite/Test Suite 2.15.txt +0 -373
  148. package/tests/suite/bin/ start +0 -0
  149. package/tests/suite/bin/adca +0 -0
  150. package/tests/suite/bin/adcax +0 -0
  151. package/tests/suite/bin/adcay +0 -0
  152. package/tests/suite/bin/adcb +0 -0
  153. package/tests/suite/bin/adcix +0 -0
  154. package/tests/suite/bin/adciy +0 -0
  155. package/tests/suite/bin/adcz +0 -0
  156. package/tests/suite/bin/adczx +0 -0
  157. package/tests/suite/bin/alrb +0 -0
  158. package/tests/suite/bin/ancb +0 -0
  159. package/tests/suite/bin/anda +0 -0
  160. package/tests/suite/bin/andax +0 -0
  161. package/tests/suite/bin/anday +0 -0
  162. package/tests/suite/bin/andb +0 -0
  163. package/tests/suite/bin/andix +0 -0
  164. package/tests/suite/bin/andiy +0 -0
  165. package/tests/suite/bin/andz +0 -0
  166. package/tests/suite/bin/andzx +0 -0
  167. package/tests/suite/bin/aneb +0 -0
  168. package/tests/suite/bin/arrb +0 -0
  169. package/tests/suite/bin/asla +0 -0
  170. package/tests/suite/bin/aslax +0 -0
  171. package/tests/suite/bin/asln +0 -0
  172. package/tests/suite/bin/aslz +0 -0
  173. package/tests/suite/bin/aslzx +0 -0
  174. package/tests/suite/bin/asoa +0 -0
  175. package/tests/suite/bin/asoax +0 -0
  176. package/tests/suite/bin/asoay +0 -0
  177. package/tests/suite/bin/asoix +0 -0
  178. package/tests/suite/bin/asoiy +0 -0
  179. package/tests/suite/bin/asoz +0 -0
  180. package/tests/suite/bin/asozx +0 -0
  181. package/tests/suite/bin/axsa +0 -0
  182. package/tests/suite/bin/axsix +0 -0
  183. package/tests/suite/bin/axsz +0 -0
  184. package/tests/suite/bin/axszy +0 -0
  185. package/tests/suite/bin/bccr +0 -0
  186. package/tests/suite/bin/bcsr +0 -0
  187. package/tests/suite/bin/beqr +0 -0
  188. package/tests/suite/bin/bita +0 -0
  189. package/tests/suite/bin/bitz +0 -0
  190. package/tests/suite/bin/bmir +0 -0
  191. package/tests/suite/bin/bner +0 -0
  192. package/tests/suite/bin/bplr +0 -0
  193. package/tests/suite/bin/branchwrap +0 -0
  194. package/tests/suite/bin/brkn +0 -0
  195. package/tests/suite/bin/bvcr +0 -0
  196. package/tests/suite/bin/bvsr +0 -0
  197. package/tests/suite/bin/cia1pb6 +0 -0
  198. package/tests/suite/bin/cia1pb7 +0 -0
  199. package/tests/suite/bin/cia1ta +0 -0
  200. package/tests/suite/bin/cia1tab +0 -0
  201. package/tests/suite/bin/cia1tb +0 -0
  202. package/tests/suite/bin/cia1tb123 +0 -0
  203. package/tests/suite/bin/cia2pb6 +0 -0
  204. package/tests/suite/bin/cia2pb7 +0 -0
  205. package/tests/suite/bin/cia2ta +0 -0
  206. package/tests/suite/bin/cia2tb +0 -0
  207. package/tests/suite/bin/cia2tb123 +0 -0
  208. package/tests/suite/bin/clcn +0 -0
  209. package/tests/suite/bin/cldn +0 -0
  210. package/tests/suite/bin/clin +0 -0
  211. package/tests/suite/bin/clvn +0 -0
  212. package/tests/suite/bin/cmpa +0 -0
  213. package/tests/suite/bin/cmpax +0 -0
  214. package/tests/suite/bin/cmpay +0 -0
  215. package/tests/suite/bin/cmpb +0 -0
  216. package/tests/suite/bin/cmpix +0 -0
  217. package/tests/suite/bin/cmpiy +0 -0
  218. package/tests/suite/bin/cmpz +0 -0
  219. package/tests/suite/bin/cmpzx +0 -0
  220. package/tests/suite/bin/cntdef +0 -0
  221. package/tests/suite/bin/cnto2 +0 -0
  222. package/tests/suite/bin/cpuport +0 -0
  223. package/tests/suite/bin/cputiming +0 -0
  224. package/tests/suite/bin/cpxa +0 -0
  225. package/tests/suite/bin/cpxb +0 -0
  226. package/tests/suite/bin/cpxz +0 -0
  227. package/tests/suite/bin/cpya +0 -0
  228. package/tests/suite/bin/cpyb +0 -0
  229. package/tests/suite/bin/cpyz +0 -0
  230. package/tests/suite/bin/dcma +0 -0
  231. package/tests/suite/bin/dcmax +0 -0
  232. package/tests/suite/bin/dcmay +0 -0
  233. package/tests/suite/bin/dcmix +0 -0
  234. package/tests/suite/bin/dcmiy +0 -0
  235. package/tests/suite/bin/dcmz +0 -0
  236. package/tests/suite/bin/dcmzx +0 -0
  237. package/tests/suite/bin/deca +0 -0
  238. package/tests/suite/bin/decax +0 -0
  239. package/tests/suite/bin/decz +0 -0
  240. package/tests/suite/bin/deczx +0 -0
  241. package/tests/suite/bin/dexn +0 -0
  242. package/tests/suite/bin/deyn +0 -0
  243. package/tests/suite/bin/eora +0 -0
  244. package/tests/suite/bin/eorax +0 -0
  245. package/tests/suite/bin/eoray +0 -0
  246. package/tests/suite/bin/eorb +0 -0
  247. package/tests/suite/bin/eorix +0 -0
  248. package/tests/suite/bin/eoriy +0 -0
  249. package/tests/suite/bin/eorz +0 -0
  250. package/tests/suite/bin/eorzx +0 -0
  251. package/tests/suite/bin/finish +0 -0
  252. package/tests/suite/bin/flipos +0 -0
  253. package/tests/suite/bin/icr01 +0 -0
  254. package/tests/suite/bin/imr +0 -0
  255. package/tests/suite/bin/inca +0 -0
  256. package/tests/suite/bin/incax +0 -0
  257. package/tests/suite/bin/incz +0 -0
  258. package/tests/suite/bin/inczx +0 -0
  259. package/tests/suite/bin/insa +0 -0
  260. package/tests/suite/bin/insax +0 -0
  261. package/tests/suite/bin/insay +0 -0
  262. package/tests/suite/bin/insix +0 -0
  263. package/tests/suite/bin/insiy +0 -0
  264. package/tests/suite/bin/insz +0 -0
  265. package/tests/suite/bin/inszx +0 -0
  266. package/tests/suite/bin/inxn +0 -0
  267. package/tests/suite/bin/inyn +0 -0
  268. package/tests/suite/bin/irq +0 -0
  269. package/tests/suite/bin/jmpi +0 -0
  270. package/tests/suite/bin/jmpw +0 -0
  271. package/tests/suite/bin/jsrw +0 -0
  272. package/tests/suite/bin/lasay +0 -0
  273. package/tests/suite/bin/laxa +0 -0
  274. package/tests/suite/bin/laxay +0 -0
  275. package/tests/suite/bin/laxix +0 -0
  276. package/tests/suite/bin/laxiy +0 -0
  277. package/tests/suite/bin/laxz +0 -0
  278. package/tests/suite/bin/laxzy +0 -0
  279. package/tests/suite/bin/ldaa +0 -0
  280. package/tests/suite/bin/ldaax +0 -0
  281. package/tests/suite/bin/ldaay +0 -0
  282. package/tests/suite/bin/ldab +0 -0
  283. package/tests/suite/bin/ldaix +0 -0
  284. package/tests/suite/bin/ldaiy +0 -0
  285. package/tests/suite/bin/ldaz +0 -0
  286. package/tests/suite/bin/ldazx +0 -0
  287. package/tests/suite/bin/ldxa +0 -0
  288. package/tests/suite/bin/ldxay +0 -0
  289. package/tests/suite/bin/ldxb +0 -0
  290. package/tests/suite/bin/ldxz +0 -0
  291. package/tests/suite/bin/ldxzy +0 -0
  292. package/tests/suite/bin/ldya +0 -0
  293. package/tests/suite/bin/ldyax +0 -0
  294. package/tests/suite/bin/ldyb +0 -0
  295. package/tests/suite/bin/ldyz +0 -0
  296. package/tests/suite/bin/ldyzx +0 -0
  297. package/tests/suite/bin/loadth +0 -0
  298. package/tests/suite/bin/lsea +0 -0
  299. package/tests/suite/bin/lseax +0 -0
  300. package/tests/suite/bin/lseay +0 -0
  301. package/tests/suite/bin/lseix +0 -0
  302. package/tests/suite/bin/lseiy +0 -0
  303. package/tests/suite/bin/lsez +0 -0
  304. package/tests/suite/bin/lsezx +0 -0
  305. package/tests/suite/bin/lsra +0 -0
  306. package/tests/suite/bin/lsrax +0 -0
  307. package/tests/suite/bin/lsrn +0 -0
  308. package/tests/suite/bin/lsrz +0 -0
  309. package/tests/suite/bin/lsrzx +0 -0
  310. package/tests/suite/bin/lxab +0 -0
  311. package/tests/suite/bin/mmu +0 -0
  312. package/tests/suite/bin/mmufetch +0 -0
  313. package/tests/suite/bin/nmi +0 -0
  314. package/tests/suite/bin/nopa +0 -0
  315. package/tests/suite/bin/nopax +0 -0
  316. package/tests/suite/bin/nopb +0 -0
  317. package/tests/suite/bin/nopn +0 -0
  318. package/tests/suite/bin/nopz +0 -0
  319. package/tests/suite/bin/nopzx +0 -0
  320. package/tests/suite/bin/oneshot +0 -0
  321. package/tests/suite/bin/oraa +0 -0
  322. package/tests/suite/bin/oraax +0 -0
  323. package/tests/suite/bin/oraay +0 -0
  324. package/tests/suite/bin/orab +0 -0
  325. package/tests/suite/bin/oraix +0 -0
  326. package/tests/suite/bin/oraiy +0 -0
  327. package/tests/suite/bin/oraz +0 -0
  328. package/tests/suite/bin/orazx +0 -0
  329. package/tests/suite/bin/phan +0 -0
  330. package/tests/suite/bin/phpn +0 -0
  331. package/tests/suite/bin/plan +0 -0
  332. package/tests/suite/bin/plpn +0 -0
  333. package/tests/suite/bin/rlaa +0 -0
  334. package/tests/suite/bin/rlaax +0 -0
  335. package/tests/suite/bin/rlaay +0 -0
  336. package/tests/suite/bin/rlaix +0 -0
  337. package/tests/suite/bin/rlaiy +0 -0
  338. package/tests/suite/bin/rlaz +0 -0
  339. package/tests/suite/bin/rlazx +0 -0
  340. package/tests/suite/bin/rola +0 -0
  341. package/tests/suite/bin/rolax +0 -0
  342. package/tests/suite/bin/roln +0 -0
  343. package/tests/suite/bin/rolz +0 -0
  344. package/tests/suite/bin/rolzx +0 -0
  345. package/tests/suite/bin/rora +0 -0
  346. package/tests/suite/bin/rorax +0 -0
  347. package/tests/suite/bin/rorn +0 -0
  348. package/tests/suite/bin/rorz +0 -0
  349. package/tests/suite/bin/rorzx +0 -0
  350. package/tests/suite/bin/rraa +0 -0
  351. package/tests/suite/bin/rraax +0 -0
  352. package/tests/suite/bin/rraay +0 -0
  353. package/tests/suite/bin/rraix +0 -0
  354. package/tests/suite/bin/rraiy +0 -0
  355. package/tests/suite/bin/rraz +0 -0
  356. package/tests/suite/bin/rrazx +0 -0
  357. package/tests/suite/bin/rtin +0 -0
  358. package/tests/suite/bin/rtsn +0 -0
  359. package/tests/suite/bin/sbca +0 -0
  360. package/tests/suite/bin/sbcax +0 -0
  361. package/tests/suite/bin/sbcay +0 -0
  362. package/tests/suite/bin/sbcb +0 -0
  363. package/tests/suite/bin/sbcb(eb) +0 -0
  364. package/tests/suite/bin/sbcix +0 -0
  365. package/tests/suite/bin/sbciy +0 -0
  366. package/tests/suite/bin/sbcz +0 -0
  367. package/tests/suite/bin/sbczx +0 -0
  368. package/tests/suite/bin/sbxb +0 -0
  369. package/tests/suite/bin/secn +0 -0
  370. package/tests/suite/bin/sedn +0 -0
  371. package/tests/suite/bin/sein +0 -0
  372. package/tests/suite/bin/shaay +0 -0
  373. package/tests/suite/bin/shaiy +0 -0
  374. package/tests/suite/bin/shsay +0 -0
  375. package/tests/suite/bin/shxay +0 -0
  376. package/tests/suite/bin/shyax +0 -0
  377. package/tests/suite/bin/staa +0 -0
  378. package/tests/suite/bin/staax +0 -0
  379. package/tests/suite/bin/staay +0 -0
  380. package/tests/suite/bin/staix +0 -0
  381. package/tests/suite/bin/staiy +0 -0
  382. package/tests/suite/bin/staz +0 -0
  383. package/tests/suite/bin/stazx +0 -0
  384. package/tests/suite/bin/stxa +0 -0
  385. package/tests/suite/bin/stxz +0 -0
  386. package/tests/suite/bin/stxzy +0 -0
  387. package/tests/suite/bin/stya +0 -0
  388. package/tests/suite/bin/styz +0 -0
  389. package/tests/suite/bin/styzx +0 -0
  390. package/tests/suite/bin/taxn +0 -0
  391. package/tests/suite/bin/tayn +0 -0
  392. package/tests/suite/bin/trap1 +0 -0
  393. package/tests/suite/bin/trap10 +0 -0
  394. package/tests/suite/bin/trap11 +0 -0
  395. package/tests/suite/bin/trap12 +0 -0
  396. package/tests/suite/bin/trap13 +0 -0
  397. package/tests/suite/bin/trap14 +0 -0
  398. package/tests/suite/bin/trap15 +0 -0
  399. package/tests/suite/bin/trap16 +0 -0
  400. package/tests/suite/bin/trap17 +0 -0
  401. package/tests/suite/bin/trap2 +0 -0
  402. package/tests/suite/bin/trap3 +0 -0
  403. package/tests/suite/bin/trap4 +0 -0
  404. package/tests/suite/bin/trap5 +0 -0
  405. package/tests/suite/bin/trap6 +0 -0
  406. package/tests/suite/bin/trap7 +0 -0
  407. package/tests/suite/bin/trap8 +0 -0
  408. package/tests/suite/bin/trap9 +0 -0
  409. package/tests/suite/bin/tsxn +0 -0
  410. package/tests/suite/bin/txan +0 -0
  411. package/tests/suite/bin/txsn +0 -0
  412. package/tests/suite/bin/tyan +0 -0
  413. package/tests/suite/cbm-hackers-post.html +0 -178
  414. package/tests/suite/cbm-hackers-post.md +0 -78
  415. package/tests/test-suite.js +0 -147
  416. package/tests/test.css +0 -7
  417. package/tests/unit/gzip/test-1 +0 -0
  418. package/tests/unit/gzip/test-1.gz +0 -0
  419. package/tests/unit/gzip/test-2 +0 -0
  420. package/tests/unit/gzip/test-2.gz +0 -0
  421. package/tests/unit/gzip/test-3 +0 -0
  422. package/tests/unit/gzip/test-3.gz +0 -0
  423. package/tests/unit/gzip/test-4 +0 -0
  424. package/tests/unit/gzip/test-4.gz +0 -0
  425. package/tests/unit/test-adc.js +0 -307
  426. package/tests/unit/test-bcd.js +0 -30
  427. package/tests/unit/test-cmos.js +0 -266
  428. package/tests/unit/test-disc-drive.js +0 -85
  429. package/tests/unit/test-disc-hfe.js +0 -347
  430. package/tests/unit/test-disc.js +0 -232
  431. package/tests/unit/test-fifo.js +0 -35
  432. package/tests/unit/test-gamepad-source.js +0 -67
  433. package/tests/unit/test-gzip.js +0 -22
  434. package/tests/unit/test-intel-fdc.js +0 -93
  435. package/tests/unit/test-keyboard.js +0 -410
  436. package/tests/unit/test-mouse-joystick-source.js +0 -128
  437. package/tests/unit/test-scheduler.js +0 -190
  438. package/tests/unit/test-serial.js +0 -154
  439. package/tests/unit/test-teletext-adaptor.js +0 -359
  440. package/tests/unit/test-tokenise.js +0 -65
  441. package/tests/unit/test-url-params.js +0 -398
  442. package/tests/unit/test-utils.js +0 -276
  443. package/tests/unit/test-video.js +0 -498
  444. package/tests/unit/test-zip.js +0 -56
  445. package/tests/unit/zip/test-mixed.zip +0 -0
  446. package/tests/unit/zip/test-rom.zip +0 -0
  447. package/tests/unit/zip/test-ssd.zip +0 -0
  448. package/tools/fir-generator.js +0 -80
  449. package/tools/vite-plugin-fir-shader.js +0 -131
  450. package/vite.config.js +0 -34
package/package.json CHANGED
@@ -7,13 +7,23 @@
7
7
  "name": "jsbeeb",
8
8
  "description": "Emulate a BBC Micro",
9
9
  "repository": "git@github.com:mattgodbolt/jsbeeb.git",
10
- "version": "1.1.1",
10
+ "version": "1.3.2",
11
11
  "//": "If you change the version of Node, it must also be updated at the top of the Dockerfile.",
12
12
  "engines": {
13
13
  "node": "22"
14
14
  },
15
15
  "type": "module",
16
16
  "main": "./src/app/app.js",
17
+ "exports": {
18
+ ".": "./src/app/app.js",
19
+ "./machine-session": "./src/machine-session.js",
20
+ "./src/*": "./src/*",
21
+ "./tests/test-machine.js": "./tests/test-machine.js"
22
+ },
23
+ "files": [
24
+ "src/",
25
+ "tests/test-machine.js"
26
+ ],
17
27
  "dependencies": {
18
28
  "@popperjs/core": "^2.11.8",
19
29
  "argparse": "^2.0.1",
@@ -21,28 +31,31 @@
21
31
  "bootswatch": "^5.3.8",
22
32
  "event-emitter-es6": "^1.1.5",
23
33
  "fflate": "^0.8.2",
24
- "jquery": "^3.7.1",
34
+ "jquery": "^4.0.0",
25
35
  "pako": "^2.1.0",
26
36
  "smoothie": "^1.36.1",
37
+ "sharp": "^0.34.5",
27
38
  "underscore": "^1.13.7"
28
39
  },
29
40
  "devDependencies": {
30
- "@vitest/coverage-v8": "^4.0.12",
31
- "eslint": "^9.39.1",
41
+ "@eslint/js": "^10.0.1",
42
+ "@vitest/coverage-v8": "^4.0.18",
43
+ "eslint": "^10.0.0",
32
44
  "eslint-config-prettier": "^10.1.8",
33
- "eslint-plugin-prettier": "^5.5.4",
45
+ "eslint-plugin-prettier": "^5.5.5",
46
+ "globals": "^17.3.0",
34
47
  "husky": "^9.1.7",
35
48
  "lint-staged": "^16.2.7",
36
- "npm-run-all": "^4.1.5",
49
+ "npm-run-all2": "^8.0.4",
37
50
  "pixelmatch": "^7.1.0",
38
- "prettier": "^3.6.2",
39
- "sharp": "^0.34.5",
40
- "vite": "^7.2.4",
51
+ "prettier": "^3.8.1",
52
+ "vite": "^7.3.1",
41
53
  "vitest": "^4.0.10"
42
54
  },
43
55
  "optionalDependencies": {
44
- "electron": "^39.0.0",
45
- "electron-builder": "^26.0.12"
56
+ "electron": "^40.4.1",
57
+ "electron-builder": "^26.8.1",
58
+ "electron-store": "^11.0.2"
46
59
  },
47
60
  "license": "GPL-3.0-or-later",
48
61
  "build": {
@@ -87,7 +100,7 @@
87
100
  "test:cpu": "node tests/test-suite.js",
88
101
  "coverage:unit": "vitest run tests/unit --coverage",
89
102
  "coverage:all-tests": "vitest run --coverage",
90
- "benchmark": "node app-bench.js",
103
+ "benchmark": "node src/app-bench.js",
91
104
  "electron": "npm run build && ELECTRON_DISABLE_SANDBOX=1 electron .",
92
105
  "electron:build": "electron-builder"
93
106
  },
package/src/6502.js CHANGED
@@ -419,6 +419,7 @@ class Tube6502 extends Base6502 {
419
419
  this.romPaged = true;
420
420
  this.pc = this.readmem(0xfffc) | (this.readmem(0xfffd) << 8);
421
421
  this.p.i = true;
422
+ this.s = (this.s - 3) & 0xff; // Simulate 3 dummy pushes during reset
422
423
  this.tube.reset(hard);
423
424
  }
424
425
 
@@ -607,11 +608,7 @@ export class Cpu6502 extends Base6502 {
607
608
  this.peripheralCycles = 0;
608
609
  this.videoCycles = 0;
609
610
 
610
- if (this.cpuMultiplier === 1 && this.videoCyclesBatch === 0) {
611
- this.polltime = this.polltimeFast;
612
- } else {
613
- this.polltime = this.polltimeSlow;
614
- }
611
+ this.polltime = this.buildPolltime();
615
612
 
616
613
  this._debugRead = this._debugWrite = this._debugInstruction = null;
617
614
  this.debugInstruction = new DebugHook(this, "_debugInstruction");
@@ -1185,6 +1182,7 @@ export class Cpu6502 extends Base6502 {
1185
1182
  }
1186
1183
  this.pc = this.readmem(0xfffc) | (this.readmem(0xfffd) << 8);
1187
1184
  this.p.i = true;
1185
+ this.s = (this.s - 3) & 0xff; // Simulate 3 dummy pushes during reset
1188
1186
  this._nmiEdge = false;
1189
1187
  this._nmiLevel = false;
1190
1188
  this.halted = false;
@@ -1211,44 +1209,56 @@ export class Cpu6502 extends Base6502 {
1211
1209
  this.polltime(cycles);
1212
1210
  }
1213
1211
 
1214
- // Common between polltimeSlow and polltimeFast
1215
- polltimeCommon(cycles) {
1216
- this.scheduler.polltime(cycles);
1217
- this.tube.execute(cycles);
1218
- if (this.teletextAdaptor) this.teletextAdaptor.polltime(cycles);
1219
- if (this.music5000) this.music5000.polltime(cycles);
1220
- if (this.econet) {
1221
- const donmi = this.econet.polltime(cycles);
1222
- if (donmi && this.econet.econetNMIEnabled) {
1223
- this.NMI(true);
1224
- }
1225
- this.filestore.polltime(cycles);
1226
- }
1227
- }
1212
+ // Builds commong code between polltimeSlow and polltimeFast
1213
+ buildPolltime() {
1214
+ const nop = (_cycles) => {};
1215
+ const tubeStuff = (cycles) => (this.model.tube ? this.tube.execute(cycles) : nop);
1216
+ const teletextStuff = this.teletextAdaptor ? (cycles) => this.teletextAdaptor.polltime(cycles) : nop;
1217
+ const musicStuff = this.music5000 ? (cycles) => this.music5000.polltime(cycles) : nop;
1218
+ const econetStuff = this.econet
1219
+ ? (cycles) => {
1220
+ const donmi = this.econet.polltime(cycles);
1221
+ if (donmi && this.econet.econetNMIEnabled) {
1222
+ this.NMI(true);
1223
+ }
1224
+ this.filestore.polltime(cycles);
1225
+ }
1226
+ : nop;
1227
+
1228
+ const commonStuff = (cycles) => {
1229
+ this.scheduler.polltime(cycles);
1230
+ tubeStuff(cycles);
1231
+ teletextStuff(cycles);
1232
+ musicStuff(cycles);
1233
+ econetStuff(cycles);
1234
+ };
1228
1235
 
1229
- // Slow version allows video batching and cpu multipliers
1230
- polltimeSlow(cycles) {
1231
- cycles |= 0;
1232
- this.currentCycles += cycles;
1233
- this.peripheralCycles += cycles;
1234
- this.videoCycles += cycles;
1235
- cycles = (this.videoCycles / this.cpuMultiplier) | 0;
1236
- if (cycles > this.videoCyclesBatch) {
1237
- this.video.polltime(cycles);
1238
- this.videoCycles -= (cycles * this.cpuMultiplier) | 0;
1236
+ if (this.cpuMultiplier === 1 && this.videoCyclesBatch === 0) {
1237
+ // Simplest, common case where multiplier is 1 and batching is off.
1238
+ return (cycles) => {
1239
+ cycles |= 0;
1240
+ this.currentCycles += cycles;
1241
+ this.video.polltime(cycles);
1242
+ commonStuff(cycles);
1243
+ };
1244
+ } else {
1245
+ // Handle speedup case.
1246
+ return (cycles) => {
1247
+ cycles |= 0;
1248
+ this.currentCycles += cycles;
1249
+ this.peripheralCycles += cycles;
1250
+ this.videoCycles += cycles;
1251
+ cycles = (this.videoCycles / this.cpuMultiplier) | 0;
1252
+ if (cycles > this.videoCyclesBatch) {
1253
+ this.video.polltime(cycles);
1254
+ this.videoCycles -= (cycles * this.cpuMultiplier) | 0;
1255
+ }
1256
+ cycles = (this.peripheralCycles / this.cpuMultiplier) | 0;
1257
+ if (!cycles) return;
1258
+ this.peripheralCycles -= (cycles * this.cpuMultiplier) | 0;
1259
+ commonStuff(cycles);
1260
+ };
1239
1261
  }
1240
- cycles = (this.peripheralCycles / this.cpuMultiplier) | 0;
1241
- if (!cycles) return;
1242
- this.peripheralCycles -= (cycles * this.cpuMultiplier) | 0;
1243
- this.polltimeCommon(cycles);
1244
- }
1245
-
1246
- // Faster, but more limited version
1247
- polltimeFast(cycles) {
1248
- cycles |= 0;
1249
- this.currentCycles += cycles;
1250
- this.video.polltime(cycles);
1251
- this.polltimeCommon(cycles);
1252
1262
  }
1253
1263
 
1254
1264
  execute(numCyclesToRun) {
package/src/app/app.js CHANGED
@@ -1,9 +1,23 @@
1
1
  "use strict";
2
- import { app, BrowserWindow, dialog, Menu, shell, nativeImage } from "electron";
2
+ import { app, BrowserWindow, dialog, ipcMain, Menu, shell, nativeImage } from "electron";
3
+ import Store from "electron-store";
3
4
  import * as fs from "fs";
4
5
  import * as path from "path";
5
6
  import { ArgumentParser } from "argparse";
6
7
 
8
+ const store = new Store();
9
+
10
+ ipcMain.on("set-title", (event, title) => {
11
+ const webContents = event.sender;
12
+ const win = BrowserWindow.fromWebContents(webContents);
13
+ if (win) win.setTitle(title);
14
+ });
15
+
16
+ ipcMain.on("save-settings", (event, settings) => {
17
+ const current = store.get("settings", {});
18
+ store.set("settings", { ...current, ...settings });
19
+ });
20
+
7
21
  const isMac = process.platform === "darwin";
8
22
 
9
23
  function getArguments() {
@@ -62,7 +76,9 @@ async function createWindow() {
62
76
  },
63
77
  });
64
78
 
65
- const query = {};
79
+ // Load saved settings, then override with command-line args
80
+ const savedSettings = store.get("settings", {});
81
+ const query = { ...savedSettings };
66
82
  if (args.disc1 && !args.noboot) query.autoboot = true;
67
83
  if (args.disc1) query.disc1 = getFileParam(args.disc1);
68
84
  if (args.disc2) query.disc2 = getFileParam(args.disc2);
@@ -95,14 +111,43 @@ function makeLoader(drive) {
95
111
  properties: ["openFile"],
96
112
  });
97
113
  if (!result.canceled) {
98
- browserWindow.webContents.send("load", {
99
- drive,
100
- path: getFileParam(result.filePaths[0]),
101
- });
114
+ const filePath = getFileParam(result.filePaths[0]);
115
+ store.set(`settings.disc${drive + 1}`, filePath);
116
+ browserWindow.webContents.send("load", { drive, path: filePath });
102
117
  }
103
118
  };
104
119
  }
105
120
 
121
+ function makeTapeLoader() {
122
+ return async (_, browserWindow) => {
123
+ const result = await dialog.showOpenDialog(browserWindow, {
124
+ title: "Load a tape image",
125
+ filters: [
126
+ { name: "Tape images", extensions: ["uef"] },
127
+ { name: "ZIPped tape images", extensions: ["zip"] },
128
+ ],
129
+ properties: ["openFile"],
130
+ });
131
+ if (!result.canceled) {
132
+ const filePath = getFileParam(result.filePaths[0]);
133
+ store.set("settings.tape", filePath);
134
+ browserWindow.webContents.send("load-tape", { path: filePath });
135
+ }
136
+ };
137
+ }
138
+
139
+ function showModal(modalId, options = {}) {
140
+ return (_, browserWindow) => {
141
+ browserWindow.webContents.send("show-modal", { modalId, ...options });
142
+ };
143
+ }
144
+
145
+ function sendAction(actionId) {
146
+ return (_, browserWindow) => {
147
+ browserWindow.webContents.send("action", { actionId });
148
+ };
149
+ }
150
+
106
151
  const template = [
107
152
  // { role: 'appMenu' }
108
153
  ...(isMac
@@ -128,13 +173,32 @@ const template = [
128
173
  label: "File",
129
174
  submenu: [
130
175
  {
131
- label: "Load disc 0",
176
+ label: "Load disc 0...",
132
177
  click: makeLoader(0),
133
178
  },
134
179
  {
135
- label: "Load disc 1",
180
+ label: "Load disc 1...",
136
181
  click: makeLoader(1),
137
182
  },
183
+ { type: "separator" },
184
+ {
185
+ label: "Browse STH Disc Archive...",
186
+ click: showModal("sth", { sthType: "discs" }),
187
+ },
188
+ {
189
+ label: "Browse Example Discs...",
190
+ click: showModal("discs"),
191
+ },
192
+ { type: "separator" },
193
+ {
194
+ label: "Load Tape from File...",
195
+ click: makeTapeLoader(),
196
+ },
197
+ {
198
+ label: "Browse STH Tape Archive...",
199
+ click: showModal("sth", { sthType: "tapes" }),
200
+ },
201
+ { type: "separator" },
138
202
  isMac ? { role: "close" } : { role: "quit" },
139
203
  ],
140
204
  },
@@ -158,9 +222,36 @@ const template = [
158
222
  { role: "togglefullscreen" },
159
223
  ],
160
224
  },
225
+ {
226
+ label: "Machine",
227
+ submenu: [
228
+ {
229
+ label: "Configuration...",
230
+ click: showModal("configuration"),
231
+ },
232
+ { type: "separator" },
233
+ {
234
+ label: "Soft Reset",
235
+ click: sendAction("soft-reset"),
236
+ },
237
+ {
238
+ label: "Hard Reset",
239
+ click: sendAction("hard-reset"),
240
+ },
241
+ ],
242
+ },
161
243
  {
162
244
  role: "help",
163
245
  submenu: [
246
+ {
247
+ label: "Help...",
248
+ click: showModal("help"),
249
+ },
250
+ {
251
+ label: "About jsbeeb...",
252
+ click: showModal("info"),
253
+ },
254
+ { type: "separator" },
164
255
  {
165
256
  label: "Learn More",
166
257
  click: async () => {
@@ -1,20 +1,62 @@
1
1
  "use strict";
2
2
 
3
3
  // Electron integration for jsbeeb desktop application.
4
- // Handles IPC communication for loading disc images from Electron's main process.
4
+ // Handles IPC communication for loading disc/tape images and showing modals from Electron's main process.
5
5
 
6
6
  export let initialise = function () {};
7
7
 
8
8
  function init(args) {
9
- const { loadDiscImage, processor } = args;
10
- const electron = window.nodeRequire("electron");
11
- electron.ipcRenderer.on("load", async (event, message) => {
9
+ const { loadDiscImage, loadTapeImage, processor, modals, actions, config } = args;
10
+ const api = window.electronAPI;
11
+
12
+ api.onLoadDisc(async (message) => {
12
13
  const { drive, path } = message;
13
14
  const image = await loadDiscImage(path);
14
15
  processor.fdc.loadDisc(drive, image);
15
16
  });
17
+
18
+ api.onLoadTape(async (message) => {
19
+ const { path } = message;
20
+ const tape = await loadTapeImage(path);
21
+ processor.acia.setTape(tape);
22
+ });
23
+
24
+ api.onShowModal((message) => {
25
+ const { modalId, sthType } = message;
26
+ if (modals && modals.show) {
27
+ modals.show(modalId, sthType);
28
+ }
29
+ });
30
+
31
+ api.onAction((message) => {
32
+ if (actions && actions[message.actionId]) {
33
+ actions[message.actionId]();
34
+ }
35
+ });
36
+
37
+ // Observe model name changes and update window title
38
+ const modelElement = document.querySelector(".bbc-model");
39
+ if (modelElement) {
40
+ const updateTitle = () => api.setTitle(`jsbeeb - ${modelElement.textContent}`);
41
+ updateTitle();
42
+ new MutationObserver(updateTitle).observe(modelElement, {
43
+ childList: true,
44
+ characterData: true,
45
+ subtree: true,
46
+ });
47
+ }
48
+
49
+ // Save settings when they change
50
+ if (config) {
51
+ config.on("settings-changed", (settings) => {
52
+ api.saveSettings(settings);
53
+ });
54
+ config.on("media-changed", (media) => {
55
+ api.saveSettings(media);
56
+ });
57
+ }
16
58
  }
17
59
 
18
- if (typeof window.nodeRequire !== "undefined") {
60
+ if (typeof window.electronAPI !== "undefined") {
19
61
  initialise = init;
20
62
  }
@@ -1,5 +1,15 @@
1
1
  "use strict";
2
- window.nodeRequire = require;
2
+ const { contextBridge, ipcRenderer } = require("electron");
3
+
4
+ // Expose a secure API to the renderer
5
+ contextBridge.exposeInMainWorld("electronAPI", {
6
+ onLoadDisc: (callback) => ipcRenderer.on("load", (event, message) => callback(message)),
7
+ onLoadTape: (callback) => ipcRenderer.on("load-tape", (event, message) => callback(message)),
8
+ onShowModal: (callback) => ipcRenderer.on("show-modal", (event, message) => callback(message)),
9
+ onAction: (callback) => ipcRenderer.on("action", (event, message) => callback(message)),
10
+ setTitle: (title) => ipcRenderer.send("set-title", title),
11
+ saveSettings: (settings) => ipcRenderer.send("save-settings", settings),
12
+ });
3
13
 
4
14
  window.addEventListener("DOMContentLoaded", () => {
5
15
  for (const node of document.getElementsByClassName("not-electron")) {
package/src/config.js CHANGED
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  import $ from "jquery";
3
+ import EventEmitter from "event-emitter-es6";
3
4
  import { findModel } from "./models.js";
4
5
  import { getFilterForMode } from "./canvas.js";
5
6
 
6
- export class Config {
7
+ export class Config extends EventEmitter {
7
8
  constructor(onChange, onClose) {
9
+ super();
8
10
  this.onChange = onChange;
9
11
  this.onClose = onClose;
10
12
  this.changed = {};
@@ -20,7 +22,12 @@ export class Config {
20
22
  this.setEconet(this.model.hasEconet);
21
23
  });
22
24
 
23
- $configuration.addEventListener("hide.bs.modal", () => this.onClose(this.changed));
25
+ $configuration.addEventListener("hide.bs.modal", () => {
26
+ this.onClose(this.changed);
27
+ if (Object.keys(this.changed).length > 0) {
28
+ this.emit("settings-changed", this.changed);
29
+ }
30
+ });
24
31
 
25
32
  $(".model-menu a").on("click", (e) => {
26
33
  this.changed.model = $(e.target).attr("data-target");
package/src/disc.js CHANGED
@@ -343,7 +343,7 @@ class Sector {
343
343
  let sectorSize = Sector.toSectorSize(sectorEndByte - sectorStartByte - 5);
344
344
 
345
345
  this.hasDataCrcError = true;
346
- let seenIffyData = false;
346
+ let seenIffyData;
347
347
  do {
348
348
  const { crcOk, sectorData, iffyPulses } = this._tryLoadSectorData(dataMarker, sectorSize);
349
349
  seenIffyData = iffyPulses;
@@ -425,7 +425,7 @@ class Track {
425
425
  const top32of64b = 0xffffffff00000000n;
426
426
  const fmMarker = 0x8888888800000000n;
427
427
  const mfmMarker = 0xaaaa448944894489n;
428
- let dataByte = 0;
428
+ let dataByte;
429
429
  let sector = null;
430
430
  for (let pulseIndex = 0; pulseIndex < bitLength; ++pulseIndex) {
431
431
  if ((pulseIndex & 31) === 0) pulses = this.pulses2Us[pulseIndex >>> 5];
package/src/filestore.js CHANGED
@@ -330,10 +330,7 @@ export class Filestore {
330
330
  this.pollCount = 0;
331
331
 
332
332
  // Decode and execute next instruction
333
- let op,
334
- i,
335
- i2,
336
- j = 0;
333
+ let op, i, i2, j;
337
334
 
338
335
  this.XPC = this.PC;
339
336
  if (this.PC >= 0xf800)
@@ -26,7 +26,7 @@ export class GamepadSource extends AnalogueSource {
26
26
  const pad = pads[0];
27
27
  const pad2 = pads[1];
28
28
 
29
- let rawValue = 0;
29
+ let rawValue;
30
30
 
31
31
  switch (channel) {
32
32
  case 0: