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,347 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { Disc, DiscConfig, IbmDiscFormat, loadSsd } from "../../src/disc.js";
4
+ import { loadHfe, toHfe, convertTrackToHfeV3 } from "../../src/disc-hfe.js";
5
+ import * as fs from "node:fs";
6
+
7
+ describe("HFE loader tests", function () {
8
+ const data = fs.readFileSync("public/discs/elite.hfe");
9
+ it("should load Elite", () => {
10
+ const disc = new Disc(true, new DiscConfig(), "test.hfe");
11
+ loadHfe(disc, data);
12
+ expect(disc.tracksUsed).toBe(81);
13
+ const sectors = disc.getTrack(false, 0).findSectors();
14
+ expect(sectors.length).toBe(10);
15
+ for (const sector of sectors) {
16
+ expect(sector.hasHeaderCrcError).toBe(false);
17
+ expect(sector.hasDataCrcError).toBe(false);
18
+ }
19
+ });
20
+
21
+ it("should reject invalid HFE files", () => {
22
+ const disc = new Disc(true, new DiscConfig(), "test.hfe");
23
+
24
+ // Test missing header
25
+ expect(() => {
26
+ loadHfe(disc, new Uint8Array(10));
27
+ }).toThrow(/HFE file missing header/);
28
+
29
+ // Test invalid header
30
+ const invalidHeader = new Uint8Array(512);
31
+ invalidHeader.set(new TextEncoder().encode("INVALID!"), 0);
32
+ expect(() => {
33
+ loadHfe(disc, invalidHeader);
34
+ }).toThrow(/HFE file bad header/);
35
+
36
+ // Test non-zero revision
37
+ const nonZeroRevision = new Uint8Array(512);
38
+ nonZeroRevision.set(new TextEncoder().encode("HXCHFEV3"), 0);
39
+ nonZeroRevision[8] = 1; // Set revision to 1 (should be 0)
40
+ expect(() => {
41
+ loadHfe(disc, nonZeroRevision);
42
+ }).toThrow(/HFE file revision not 0/);
43
+
44
+ // Test unsupported encoding
45
+ const unsupportedEncoding = new Uint8Array(512);
46
+ unsupportedEncoding.set(new TextEncoder().encode("HXCHFEV3"), 0);
47
+ unsupportedEncoding[8] = 0; // Revision 0
48
+ unsupportedEncoding[11] = 1; // Encoding 1 (not 0 or 2)
49
+ expect(() => {
50
+ loadHfe(disc, unsupportedEncoding);
51
+ }).toThrow(/HFE encoding not ISOIBM/);
52
+ });
53
+ });
54
+
55
+ describe(
56
+ "HFE round-trip tests",
57
+ {
58
+ timeout: 120000, // HFE processing can be slow
59
+ },
60
+ function () {
61
+ const data = fs.readFileSync("public/discs/elite.hfe");
62
+ it("should round-trip elite.hfe", () => {
63
+ // Load the original HFE file
64
+ const disc = new Disc(true, new DiscConfig(), "test.hfe");
65
+ loadHfe(disc, data);
66
+
67
+ // Export it back to HFE
68
+ const hfeSaved = toHfe(disc);
69
+
70
+ // Load the saved HFE into a new disc
71
+ const disc2 = new Disc(true, new DiscConfig(), "test2.hfe");
72
+ loadHfe(disc2, hfeSaved);
73
+
74
+ // Verify that both discs have the same properties
75
+ expect(disc.tracksUsed).toBe(disc2.tracksUsed);
76
+ expect(disc.isDoubleSided).toBe(disc2.isDoubleSided);
77
+
78
+ // Compare sectors in a few sample tracks
79
+ const trackSamples = [0, 10, 20, 40]; // Sample a few tracks
80
+ for (const trackNum of trackSamples) {
81
+ if (trackNum >= disc.tracksUsed) continue;
82
+
83
+ const track1 = disc.getTrack(false, trackNum);
84
+ const track2 = disc2.getTrack(false, trackNum);
85
+
86
+ // With our variable track length HFE implementation,
87
+ // track lengths should be identical after roundtripping
88
+
89
+ // Track lengths should be identical when roundtripping with the variable track length HFE implementation
90
+ expect(track1.length).toBe(track2.length);
91
+
92
+ // Compare sectors - this is the most important test
93
+ // All sectors must be readable and contain the correct data
94
+ const sectors1 = track1.findSectors();
95
+ const sectors2 = track2.findSectors();
96
+
97
+ // All sectors must be found
98
+ expect(sectors1.length).toBe(sectors2.length);
99
+
100
+ // Compare sector data for first sector as a sample
101
+ if (sectors1.length > 0 && sectors2.length > 0) {
102
+ expect(sectors1[0].sectorNumber).toBe(sectors2[0].sectorNumber);
103
+ expect(sectors1[0].trackNumber).toBe(sectors2[0].trackNumber);
104
+
105
+ // Compare actual sector data if available
106
+ if (sectors1[0].sectorData && sectors2[0].sectorData) {
107
+ expect(sectors1[0].sectorData.length).toBe(sectors2[0].sectorData.length);
108
+
109
+ // Sample a few bytes from the sector
110
+ if (sectors1[0].sectorData.length > 0) {
111
+ expect(sectors1[0].sectorData[0]).toBe(sectors2[0].sectorData[0]);
112
+
113
+ const midPoint = Math.floor(sectors1[0].sectorData.length / 2);
114
+ expect(sectors1[0].sectorData[midPoint]).toBe(sectors2[0].sectorData[midPoint]);
115
+
116
+ expect(sectors1[0].sectorData[sectors1[0].sectorData.length - 1]).toBe(
117
+ sectors2[0].sectorData[sectors2[0].sectorData.length - 1],
118
+ );
119
+ }
120
+ }
121
+ }
122
+ }
123
+ });
124
+ },
125
+ );
126
+
127
+ describe("HFE export tests", function () {
128
+ it("should export a simple single-sided disc", { timeout: 10000 }, () => {
129
+ const disc = new Disc(true, new DiscConfig(), "test.hfe");
130
+ const sectorData = new Uint8Array(256);
131
+ sectorData.fill(0xa5);
132
+
133
+ // Create a simple FM track
134
+ const builder = disc.buildTrack(false, 0);
135
+ builder
136
+ .appendRepeatFmByte(0xff, IbmDiscFormat.stdGap1FFs)
137
+ .appendRepeatFmByte(0x00, IbmDiscFormat.stdSync00s)
138
+ .resetCrc()
139
+ .appendFmDataAndClocks(IbmDiscFormat.idMarkDataPattern, IbmDiscFormat.markClockPattern)
140
+ .appendFmByte(0) // track
141
+ .appendFmByte(0)
142
+ .appendFmByte(0) // sector
143
+ .appendFmByte(1)
144
+ .appendCrc(false)
145
+ .appendRepeatFmByte(0xff, IbmDiscFormat.stdGap2FFs)
146
+ .appendRepeatFmByte(0x00, IbmDiscFormat.stdSync00s)
147
+ .resetCrc()
148
+ .appendFmDataAndClocks(IbmDiscFormat.dataMarkDataPattern, IbmDiscFormat.markClockPattern)
149
+ .appendFmChunk(sectorData)
150
+ .appendCrc(false)
151
+ .fillFmByte(0xff);
152
+
153
+ const hfeData = toHfe(disc);
154
+
155
+ // Verify HFE header
156
+ const header = new TextDecoder("ascii").decode(hfeData.slice(0, 8));
157
+ expect(header).toBe("HXCHFEV3");
158
+ expect(hfeData[8]).toBe(0); // Revision
159
+ expect(hfeData[9]).toBe(1); // Number of tracks
160
+ expect(hfeData[10]).toBe(1); // Number of sides
161
+ expect(hfeData[11]).toBe(2); // ISOIBM_FM_MFM_ENCODING
162
+ });
163
+
164
+ it("should export and reload the same disc", { timeout: 30000 }, () => {
165
+ // Create a disc with FM data
166
+ const disc = new Disc(true, new DiscConfig(), "test.hfe");
167
+ const data = new Uint8Array(10240); // 10 sectors worth
168
+ for (let i = 0; i < data.length; i++) {
169
+ data[i] = i & 0xff;
170
+ }
171
+ loadSsd(disc, data, false);
172
+
173
+ // Export to HFE
174
+ const hfeData = toHfe(disc);
175
+
176
+ // Reload from HFE
177
+ const disc2 = new Disc(true, new DiscConfig(), "test2.hfe");
178
+ loadHfe(disc2, hfeData);
179
+
180
+ // Compare track data
181
+ expect(disc.tracksUsed).toBe(disc2.tracksUsed);
182
+ for (let trackNum = 0; trackNum < disc.tracksUsed; trackNum++) {
183
+ const track1 = disc.getTrack(false, trackNum);
184
+ const track2 = disc2.getTrack(false, trackNum);
185
+
186
+ // With our improved implementation, track lengths should be exactly the same
187
+ // when round-tripping through the HFE format
188
+ expect(track1.length).toBe(track2.length);
189
+
190
+ // Find sectors and compare
191
+ const sectors1 = track1.findSectors();
192
+ const sectors2 = track2.findSectors();
193
+
194
+ // All sectors must be found - this is the critical test
195
+ expect(sectors1.length).toBe(sectors2.length);
196
+
197
+ expect(sectors1.length).toBe(sectors2.length);
198
+
199
+ for (let i = 0; i < sectors1.length; i++) {
200
+ expect(sectors1[i].sectorNumber).toBe(sectors2[i].sectorNumber);
201
+ expect(sectors1[i].trackNumber).toBe(sectors2[i].trackNumber);
202
+ // Compare sector data if available
203
+ if (sectors1[i].sectorData && sectors2[i].sectorData) {
204
+ expect(sectors1[i].sectorData).toEqual(sectors2[i].sectorData);
205
+ }
206
+ }
207
+ }
208
+ });
209
+
210
+ it("should export a double-sided disc", { timeout: 10000 }, () => {
211
+ const disc = new Disc(true, new DiscConfig(), "test.hfe");
212
+ const data = new Uint8Array(10240); // 10 sectors worth
213
+ data.fill(0xbb);
214
+ loadSsd(disc, data, true); // Load as DSD (double-sided)
215
+
216
+ const hfeData = toHfe(disc);
217
+
218
+ // Verify HFE header
219
+ const header = new TextDecoder("ascii").decode(hfeData.slice(0, 8));
220
+ expect(header).toBe("HXCHFEV3");
221
+ expect(hfeData[10]).toBe(2); // Number of sides
222
+
223
+ // Reload and verify
224
+ const disc2 = new Disc(true, new DiscConfig(), "test2.hfe");
225
+ loadHfe(disc2, hfeData);
226
+
227
+ expect(disc2.isDoubleSided).toBe(true);
228
+ expect(disc.tracksUsed).toBe(disc2.tracksUsed);
229
+ });
230
+
231
+ it("should properly handle MFM data", { timeout: 10000 }, () => {
232
+ const disc = new Disc(true, new DiscConfig(), "test.hfe");
233
+ const sectorData = new Uint8Array(256);
234
+ for (let i = 0; i < sectorData.length; i++) {
235
+ sectorData[i] = (i * 0x11) & 0xff;
236
+ }
237
+
238
+ // Create an MFM track
239
+ const builder = disc.buildTrack(false, 0);
240
+ builder
241
+ .appendRepeatMfmByte(0x4e, 60)
242
+ .appendRepeatMfmByte(0x00, 12)
243
+ .resetCrc()
244
+ .appendMfm3xA1Sync()
245
+ .appendMfmByte(IbmDiscFormat.idMarkDataPattern)
246
+ .appendMfmByte(0) // track
247
+ .appendMfmByte(0)
248
+ .appendMfmByte(0) // sector
249
+ .appendMfmByte(1)
250
+ .appendCrc(true)
251
+ .appendRepeatMfmByte(0x4e, 22)
252
+ .appendRepeatMfmByte(0x00, 12)
253
+ .resetCrc()
254
+ .appendMfm3xA1Sync()
255
+ .appendMfmByte(IbmDiscFormat.dataMarkDataPattern)
256
+ .appendMfmChunk(sectorData)
257
+ .appendCrc(true)
258
+ .appendRepeatMfmByte(0x4e, 24)
259
+ .fillMfmByte(0x4e);
260
+
261
+ const hfeData = toHfe(disc);
262
+
263
+ // Reload and verify
264
+ const disc2 = new Disc(true, new DiscConfig(), "test2.hfe");
265
+ loadHfe(disc2, hfeData);
266
+
267
+ const sectors = disc2.getTrack(false, 0).findSectors();
268
+ expect(sectors.length).toBe(1);
269
+ expect(sectors[0].isMfm).toBe(true);
270
+ expect(sectors[0].sectorData).toEqual(sectorData);
271
+ });
272
+ });
273
+
274
+ describe("HFE track conversion tests", function () {
275
+ it("should handle weak pulses correctly", () => {
276
+ // Create an array with some normal pulses and some weak pulses (0)
277
+ const pulses = [0xaabbccdd, 0, 0x11223344, 0x55667788, 0];
278
+
279
+ // Convert to HFE v3 format
280
+ const hfeData = convertTrackToHfeV3(pulses);
281
+
282
+ // Check for the track header (SETINDEX, SETBITRATE, Bitrate250k)
283
+ expect(hfeData.length).toBe(3 + pulses.length * 4);
284
+
285
+ // Check that weak pulses (value 0) are handled specially
286
+ // They should be encoded as the RAND opcode (0xF4) with bit flipping
287
+ // The RAND opcode after bit flipping is 0x2F
288
+ const randOpcodeFlipped = 0x2f;
289
+
290
+ // Check the second pulse (index 1) which is a weak pulse
291
+ expect(hfeData[3 + 4]).toBe(randOpcodeFlipped);
292
+ expect(hfeData[3 + 5]).toBe(randOpcodeFlipped);
293
+ expect(hfeData[3 + 6]).toBe(randOpcodeFlipped);
294
+ expect(hfeData[3 + 7]).toBe(randOpcodeFlipped);
295
+
296
+ // Also check the fifth pulse (index 4) which is also a weak pulse
297
+ expect(hfeData[3 + 16]).toBe(randOpcodeFlipped);
298
+ expect(hfeData[3 + 17]).toBe(randOpcodeFlipped);
299
+ expect(hfeData[3 + 18]).toBe(randOpcodeFlipped);
300
+ expect(hfeData[3 + 19]).toBe(randOpcodeFlipped);
301
+ });
302
+
303
+ it("should replace pulses with first byte 0xf0 to avoid collision", () => {
304
+ // When bit-flipped, 0xf0 becomes 0x0f, which triggers opcode collision
305
+ // The RAND opcode (0xf4) bit-flipped is 0x2f (47 decimal)
306
+ const randOpcodeFlipped = 0x2f;
307
+
308
+ // Create a pulse with 0xf0 as the first byte
309
+ const pulseWithF0 = 0xf0aabbcc;
310
+
311
+ // Convert to HFE v3 format
312
+ const hfeData = convertTrackToHfeV3([pulseWithF0]);
313
+
314
+ // When we detect this kind of collision, we replace with the bit-flipped RAND opcode
315
+ expect(hfeData[3]).toBe(randOpcodeFlipped);
316
+ });
317
+
318
+ it("should handle bit flipping for pulses starting with 0x0f", () => {
319
+ // Create a pulse with 0x0f as most significant byte
320
+ const pulseWithF = 0x0f000000;
321
+
322
+ // Convert to HFE v3 format
323
+ const hfeData = convertTrackToHfeV3([pulseWithF]);
324
+
325
+ // The flipped value (0xf0) is returned directly (no opcode collision detected)
326
+ // because 0xf0 doesn't match the collision detection pattern
327
+ expect(hfeData[3]).toBe(0xf0);
328
+ });
329
+
330
+ it("should preserve normal pulses correctly", () => {
331
+ // Create a normal pulse that doesn't have opcode collisions
332
+ const normalPulse = 0x12345678;
333
+
334
+ // Convert to HFE v3 format
335
+ const hfeData = convertTrackToHfeV3([normalPulse]);
336
+
337
+ // Check the bytes match what we expect after bit flipping
338
+ // 0x12 after bit flipping becomes 0x48
339
+ // 0x34 after bit flipping becomes 0x2C
340
+ // 0x56 after bit flipping becomes 0x6A
341
+ // 0x78 after bit flipping becomes 0x1E
342
+ expect(hfeData[3]).toBe(0x48);
343
+ expect(hfeData[4]).toBe(0x2c);
344
+ expect(hfeData[5]).toBe(0x6a);
345
+ expect(hfeData[6]).toBe(0x1e);
346
+ });
347
+ });
@@ -0,0 +1,232 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { Disc, DiscConfig, IbmDiscFormat, loadSsd, loadAdf, toSsdOrDsd } from "../../src/disc.js";
4
+ import * as fs from "node:fs";
5
+
6
+ describe("IBM disc format tests", function () {
7
+ it("calculates FM crcs", () => {
8
+ let crc = IbmDiscFormat.crcInit(false);
9
+ crc = IbmDiscFormat.crcAddByte(crc, 0x12);
10
+ crc = IbmDiscFormat.crcAddByte(crc, 0x34);
11
+ crc = IbmDiscFormat.crcAddByte(crc, 0x56);
12
+ crc = IbmDiscFormat.crcAddByte(crc, 0x70);
13
+ expect(crc).toBe(0xb1e4);
14
+ });
15
+ it("calculates MFM crcs", () => {
16
+ let crc = IbmDiscFormat.crcInit(true);
17
+ crc = IbmDiscFormat.crcAddByte(crc, 0x12);
18
+ crc = IbmDiscFormat.crcAddByte(crc, 0x34);
19
+ crc = IbmDiscFormat.crcAddByte(crc, 0x56);
20
+ crc = IbmDiscFormat.crcAddByte(crc, 0x70);
21
+ expect(crc).toBe(0x9d39);
22
+ });
23
+ it("converts to FM pulses", () => {
24
+ expect(IbmDiscFormat.fmTo2usPulses(0xff, 0x00)).toBe(0x44444444);
25
+ expect(IbmDiscFormat.fmTo2usPulses(0xff, 0xff)).toBe(0x55555555);
26
+ expect(IbmDiscFormat.fmTo2usPulses(0xc7, 0xfe)).toBe(0x55111554);
27
+ });
28
+ it("converts from FM pulses", () => {
29
+ // TODO either fix these or understand why beebjit doesn't use same bit posn for bits.
30
+ const deliberateFudge = 1;
31
+ expect(IbmDiscFormat._2usPulsesToFm(0x44444444 << deliberateFudge)).toEqual({
32
+ clocks: 0xff,
33
+ data: 0x00,
34
+ iffyPulses: false,
35
+ });
36
+ expect(IbmDiscFormat._2usPulsesToFm(0x55555555 << deliberateFudge)).toEqual({
37
+ clocks: 0xff,
38
+ data: 0xff,
39
+ iffyPulses: false,
40
+ });
41
+ expect(IbmDiscFormat._2usPulsesToFm(0x55111554 << deliberateFudge)).toEqual({
42
+ clocks: 0xc7,
43
+ data: 0xfe,
44
+ iffyPulses: false,
45
+ });
46
+ expect(IbmDiscFormat._2usPulsesToFm((0x55111554 << deliberateFudge) | 0x05)).toEqual({
47
+ clocks: 0xc7,
48
+ data: 0xfe,
49
+ iffyPulses: true,
50
+ });
51
+ });
52
+ it("converts to MFM pulses", () => {
53
+ expect(IbmDiscFormat.mfmTo2usPulses(false, 0x00)).toEqual({ lastBit: false, pulses: 0xaaaa });
54
+ expect(IbmDiscFormat.mfmTo2usPulses(true, 0x00)).toEqual({ lastBit: false, pulses: 0x2aaa });
55
+ expect(IbmDiscFormat.mfmTo2usPulses(false, 0xff)).toEqual({ lastBit: true, pulses: 0x5555 });
56
+ expect(IbmDiscFormat.mfmTo2usPulses(true, 0xff)).toEqual({ lastBit: true, pulses: 0x5555 });
57
+ expect(IbmDiscFormat.mfmTo2usPulses(false, 0x37)).toEqual({ lastBit: true, pulses: 0xa515 });
58
+ expect(IbmDiscFormat.mfmTo2usPulses(true, 0x37)).toEqual({ lastBit: true, pulses: 0x2515 });
59
+ });
60
+ it("Converts from MFM pulses", () => {
61
+ expect(IbmDiscFormat._2usPulsesToMfm(0xaaaa)).toBe(0);
62
+ expect(IbmDiscFormat._2usPulsesToMfm(0x2aaa)).toBe(0);
63
+ expect(IbmDiscFormat._2usPulsesToMfm(0x5555)).toBe(0xff);
64
+ expect(IbmDiscFormat._2usPulsesToMfm(0xa515)).toBe(0x37);
65
+ expect(IbmDiscFormat._2usPulsesToMfm(0x2515)).toBe(0x37);
66
+ });
67
+ it("checks gaps between MFM pulses", () => {
68
+ expect(IbmDiscFormat.checkPulse(0.0, true)).toBe(false);
69
+ expect(IbmDiscFormat.checkPulse(3.49, true)).toBe(false);
70
+ expect(IbmDiscFormat.checkPulse(4.51, true)).toBe(false);
71
+ expect(IbmDiscFormat.checkPulse(7.49, true)).toBe(false);
72
+ expect(IbmDiscFormat.checkPulse(8.51, true)).toBe(false);
73
+
74
+ expect(IbmDiscFormat.checkPulse(4.0, true)).toBe(true);
75
+ expect(IbmDiscFormat.checkPulse(5.51, true)).toBe(true);
76
+ expect(IbmDiscFormat.checkPulse(6.0, true)).toBe(true);
77
+ expect(IbmDiscFormat.checkPulse(6.49, true)).toBe(true);
78
+ expect(IbmDiscFormat.checkPulse(8.0, true)).toBe(true);
79
+ });
80
+ it("checks gaps between FM pulses", () => {
81
+ expect(IbmDiscFormat.checkPulse(0.0, false)).toBe(false);
82
+ expect(IbmDiscFormat.checkPulse(3.49, false)).toBe(false);
83
+ expect(IbmDiscFormat.checkPulse(4.51, false)).toBe(false);
84
+ expect(IbmDiscFormat.checkPulse(5.51, false)).toBe(false);
85
+ expect(IbmDiscFormat.checkPulse(6.0, false)).toBe(false);
86
+ expect(IbmDiscFormat.checkPulse(6.49, false)).toBe(false);
87
+ expect(IbmDiscFormat.checkPulse(7.49, false)).toBe(false);
88
+ expect(IbmDiscFormat.checkPulse(8.51, false)).toBe(false);
89
+
90
+ expect(IbmDiscFormat.checkPulse(4.0, false)).toBe(true);
91
+ expect(IbmDiscFormat.checkPulse(8.0, false)).toBe(true);
92
+ });
93
+ });
94
+
95
+ describe("Disc builder tests", () => {
96
+ const someData = new Uint8Array(256);
97
+ someData.fill(0x33);
98
+ it("should write a simple FM track without blowing up", () => {
99
+ const disc = new Disc(true, new DiscConfig(), "test.ssd");
100
+ const builder = disc.buildTrack(false, 0);
101
+ builder
102
+ .appendRepeatFmByte(0xff, IbmDiscFormat.stdGap1FFs)
103
+ .appendRepeatFmByte(0x00, IbmDiscFormat.stdSync00s)
104
+ .resetCrc()
105
+ .appendFmDataAndClocks(IbmDiscFormat.idMarkDataPattern, IbmDiscFormat.markClockPattern)
106
+ .appendFmByte(0) // track
107
+ .appendFmByte(0)
108
+ .appendFmByte(0) // sector
109
+ .appendFmByte(1)
110
+ .appendCrc(false)
111
+ .appendRepeatFmByte(0xff, IbmDiscFormat.stdGap2FFs)
112
+ .appendRepeatFmByte(0x00, IbmDiscFormat.stdSync00s)
113
+ .resetCrc()
114
+ .appendFmDataAndClocks(IbmDiscFormat.dataMarkDataPattern, IbmDiscFormat.markClockPattern)
115
+ .appendFmChunk(someData)
116
+ .appendCrc(false)
117
+ .fillFmByte(0xff);
118
+ });
119
+
120
+ it("should write a simple MFM track without blowing up", () => {
121
+ const disc = new Disc(true, new DiscConfig());
122
+ const builder = disc.buildTrack(false, 0);
123
+ builder
124
+ .appendRepeatMfmByte(0x4e, 60)
125
+ .appendRepeatMfmByte(0x00, 12)
126
+ .resetCrc()
127
+ .appendMfm3xA1Sync()
128
+ .appendMfmByte(IbmDiscFormat.idMarkDataPattern)
129
+ .appendMfmByte(0) // track
130
+ .appendMfmByte(0)
131
+ .appendMfmByte(0) // sector
132
+ .appendMfmByte(1)
133
+ .appendCrc(true)
134
+ .appendRepeatMfmByte(0x4e, 22)
135
+ .appendRepeatMfmByte(0x00, 12)
136
+ .resetCrc()
137
+ .appendMfm3xA1Sync()
138
+ .appendMfmByte(IbmDiscFormat.dataMarkDataPattern)
139
+ .appendMfmChunk(someData)
140
+ .appendCrc(true)
141
+ .appendRepeatMfmByte(0x4e, 24)
142
+ .fillMfmByte(0x4e);
143
+ });
144
+
145
+ it("should note how much disc is being used", () => {
146
+ const disc = new Disc(true, new DiscConfig(), "test.ssd");
147
+ expect(disc.tracksUsed).toBe(0);
148
+ expect(disc.isDoubleSided).toBe(false);
149
+ disc.buildTrack(false, 0);
150
+ expect(disc.tracksUsed).toBe(1);
151
+ expect(disc.isDoubleSided).toBe(false);
152
+ disc.buildTrack(false, 3);
153
+ expect(disc.tracksUsed).toBe(4);
154
+ expect(disc.isDoubleSided).toBe(false);
155
+ disc.buildTrack(true, 1);
156
+ expect(disc.tracksUsed).toBe(4);
157
+ expect(disc.isDoubleSided).toBe(true);
158
+ });
159
+
160
+ it("should build from FM pulses", () => {
161
+ const disc = new Disc(true, new DiscConfig(), "test.ssd");
162
+ const builder = disc.buildTrack(false, 0);
163
+ const pulses = [4, 4, 8, 8, 4, 8, 8, 8, 8, 8, 8, 8, 8];
164
+ builder.buildFromPulses(pulses, false);
165
+ expect(builder.track.length).toBe(1);
166
+ });
167
+
168
+ it("should build from MFM pulses", () => {
169
+ const disc = new Disc(true, new DiscConfig(), "test.ssd");
170
+ const builder = disc.buildTrack(false, 0);
171
+ const pulses = [4, 4, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6];
172
+ builder.buildFromPulses(pulses, true);
173
+ expect(builder.track.length).toBe(1);
174
+ });
175
+ });
176
+
177
+ describe(
178
+ "SSD loader tests",
179
+ {
180
+ timeout: 60000, // roundtripping elite can be slow
181
+ },
182
+ function () {
183
+ const data = fs.readFileSync("public/discs/elite.ssd");
184
+ it("should load Elite", () => {
185
+ const disc = new Disc(true, new DiscConfig(), "test.ssd");
186
+ loadSsd(disc, data, false);
187
+ expect(disc.tracksUsed).toBe(80);
188
+ });
189
+ it("should roundtrip Elite", () => {
190
+ const disc = new Disc(true, new DiscConfig(), "test.ssd");
191
+ loadSsd(disc, data, false);
192
+ const ssdSaved = toSsdOrDsd(disc);
193
+ // // Check the first few bytes, else a diff blows things up
194
+ const maxDiff = 50;
195
+ expect(ssdSaved.slice(0, maxDiff)).toEqual(new Uint8Array(data.slice(0, maxDiff)));
196
+
197
+ // But also check everything else; and the padding should be all zeros.
198
+ expect(ssdSaved.length >= data.length).toBe(true);
199
+ for (let i = 0; i < data.length; ++i) {
200
+ expect(ssdSaved[i]).toBe(data[i]);
201
+ }
202
+ for (let i = data.length; i < ssdSaved.length; ++i) {
203
+ expect(ssdSaved[i]).toBe(0);
204
+ }
205
+ });
206
+ it("should have sane tracks", () => {
207
+ const disc = new Disc(true, new DiscConfig(), "test.ssd");
208
+ loadSsd(disc, data, false);
209
+ const sectors = disc.getTrack(false, 0).findSectors();
210
+ expect(sectors.length).toBe(10);
211
+ for (const sector of sectors) {
212
+ expect(sector.hasHeaderCrcError).toBe(false);
213
+ expect(sector.hasDataCrcError).toBe(false);
214
+ }
215
+ });
216
+ },
217
+ );
218
+
219
+ describe("ADF loader tests", function () {
220
+ it("should load a somewhat blank ADFS disc", () => {
221
+ const data = new Uint8Array(327680);
222
+ const disc = new Disc(true, new DiscConfig(), "test.adf");
223
+ loadAdf(disc, data, true);
224
+ expect(disc.tracksUsed).toBe(40);
225
+ const sectors = disc.getTrack(false, 0).findSectors();
226
+ expect(sectors.length).toBe(16);
227
+ for (const sector of sectors) {
228
+ expect(sector.hasHeaderCrcError).toBe(false);
229
+ expect(sector.hasDataCrcError).toBe(false);
230
+ }
231
+ });
232
+ });
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { Fifo } from "../../src/utils.js";
4
+
5
+ describe("FIFO tests", function () {
6
+ "use strict";
7
+ it("creates ok", function () {
8
+ new Fifo(16);
9
+ });
10
+ it("works for simple cases", function () {
11
+ const f = new Fifo(16);
12
+ expect(f.size).toBe(0);
13
+ f.put(123);
14
+ expect(f.size).toBe(1);
15
+ expect(f.get()).toBe(123);
16
+ expect(f.size).toBe(0);
17
+ });
18
+
19
+ it("works when full", function () {
20
+ const f = new Fifo(4);
21
+ expect(f.size).toBe(0);
22
+ f.put(123);
23
+ f.put(125);
24
+ f.put(126);
25
+ f.put(127);
26
+ expect(f.size).toBe(4);
27
+ f.put(100);
28
+ expect(f.size).toBe(4);
29
+ expect(f.get()).toBe(123);
30
+ expect(f.get()).toBe(125);
31
+ expect(f.get()).toBe(126);
32
+ expect(f.get()).toBe(127);
33
+ expect(f.size).toBe(0);
34
+ });
35
+ });
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { GamepadSource } from "../../src/gamepad-source.js";
3
+
4
+ describe("GamepadSource", () => {
5
+ // Mock gamepads with various axis positions
6
+ const mockGamepads = [
7
+ {
8
+ axes: [0.5, -0.5, 0.25, -0.75], // pad1: right, up, slight right, mostly up
9
+ },
10
+ {
11
+ axes: [-0.8, 0.3], // pad2: mostly left, slight down
12
+ },
13
+ ];
14
+
15
+ let gamepadSource;
16
+
17
+ beforeEach(() => {
18
+ // Create a fake getGamepads function that returns our mocks
19
+ const getGamepads = () => mockGamepads;
20
+ gamepadSource = new GamepadSource(getGamepads);
21
+ });
22
+
23
+ describe("getValue", () => {
24
+ it("should convert gamepad axis 0 value correctly", () => {
25
+ // First axis of first gamepad is 0.5
26
+ // Formula: Math.floor(((1 - 0.5) / 2) * 0xffff)
27
+ // = Math.floor(0.25 * 0xffff) = 0x3fff
28
+ const value = gamepadSource.getValue(0);
29
+ expect(value).toBe(0x3fff);
30
+ });
31
+
32
+ it("should convert gamepad axis 1 value correctly", () => {
33
+ // Second axis of first gamepad is -0.5
34
+ // Formula: Math.floor(((1 - (-0.5)) / 2) * 0xffff)
35
+ // = Math.floor(0.75 * 0xffff) = 0xbfff
36
+ const value = gamepadSource.getValue(1);
37
+ expect(value).toBe(0xbfff);
38
+ });
39
+
40
+ it("should use second gamepad for channel 2 if available", () => {
41
+ // First axis of second gamepad is -0.8
42
+ // Formula: Math.floor(((1 - (-0.8)) / 2) * 0xffff)
43
+ // = Math.floor(0.9 * 0xffff) ≈ 58981
44
+ const value = gamepadSource.getValue(2);
45
+ expect(value).toBe(58981);
46
+ });
47
+
48
+ it("should use second gamepad for channel 3 if available", () => {
49
+ // Second axis of second gamepad is 0.3
50
+ // Formula: Math.floor(((1 - 0.3) / 2) * 0xffff)
51
+ // = Math.floor(0.35 * 0xffff) ≈ 22937
52
+ const value = gamepadSource.getValue(3);
53
+ expect(value).toBe(22937);
54
+ });
55
+
56
+ it("should return center value (0x8000) when no gamepads are connected", () => {
57
+ // Create a source that returns no gamepads
58
+ const noGamepadsSource = new GamepadSource(() => []);
59
+ expect(noGamepadsSource.getValue(0)).toBe(0x8000);
60
+ });
61
+
62
+ it("should return center value for invalid channel", () => {
63
+ expect(gamepadSource.getValue(4)).toBe(0x8000);
64
+ expect(gamepadSource.getValue(-1)).toBe(0x8000);
65
+ });
66
+ });
67
+ });