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
@@ -1,266 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
- import { Cmos } from "../../src/cmos.js";
3
-
4
- describe("CMOS", () => {
5
- // Mock persistence
6
- const mockPersistence = {
7
- load: vi.fn().mockReturnValue(null),
8
- save: vi.fn(),
9
- };
10
-
11
- // Test date (2023-04-15T12:34:56)
12
- const TEST_DATE = new Date(2023, 3, 15, 12, 34, 56);
13
-
14
- // CMOS register addresses (from BBC Micro documentation)
15
- const CMOS_ADDR = {
16
- SECONDS: 0,
17
- MINUTES: 2,
18
- HOURS: 4,
19
- DAY_OF_WEEK: 6,
20
- DAY_OF_MONTH: 7,
21
- MONTH: 8,
22
- YEAR: 9,
23
- // Non-RTC addresses for testing
24
- CONFIG_1: 12,
25
- CONFIG_2: 13,
26
- };
27
-
28
- // Constants from the hardware implementation
29
- const PORT_B_ENABLE = 0x40; // Bit 6 of port B
30
- const PORT_B_ADDR_SEL = 0x80; // Bit 7 of port B
31
- const IC32_READ = 2; // Bit 1 of IC32
32
- const IC32_DATA_SEL = 4; // Bit 2 of IC32
33
-
34
- let cmos;
35
-
36
- beforeEach(() => {
37
- // Use fake timers for consistent date/time testing
38
- vi.useFakeTimers();
39
- vi.setSystemTime(TEST_DATE);
40
-
41
- // Create a fresh CMOS instance for each test
42
- cmos = new Cmos(mockPersistence);
43
- });
44
-
45
- afterEach(() => {
46
- vi.useRealTimers();
47
- vi.resetAllMocks();
48
- });
49
-
50
- describe("Initialization", () => {
51
- it("should initialize with persistence and save default values", () => {
52
- expect(mockPersistence.save).toHaveBeenCalled();
53
- });
54
-
55
- it("should use custom persistence data if available", () => {
56
- const customData = Array(48).fill(0x42);
57
- mockPersistence.load.mockReturnValueOnce(customData);
58
-
59
- const customCmos = new Cmos(mockPersistence);
60
-
61
- // Reading from a non-RTC location should return our custom data
62
- // First enable CMOS and set it up for reading
63
- customCmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_1, 0);
64
- customCmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_1, 0);
65
- customCmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
66
-
67
- expect(customCmos.read()).toBe(0x42);
68
- });
69
-
70
- it("should apply CMOS override when provided", () => {
71
- const cmosOverride = (store) => {
72
- const newStore = [...store];
73
- newStore[CMOS_ADDR.CONFIG_1] = 0x42;
74
- return newStore;
75
- };
76
-
77
- const customCmos = new Cmos(mockPersistence, cmosOverride);
78
-
79
- // Reading from the overridden location should return our custom value
80
- // First enable CMOS and set it up for reading
81
- customCmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_1, 0);
82
- customCmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_1, 0);
83
- customCmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
84
-
85
- expect(customCmos.read()).toBe(0x42);
86
- });
87
-
88
- it("should apply econet settings when provided", () => {
89
- const econet = { stationId: 0x42 };
90
- const customCmos = new Cmos(mockPersistence, null, econet);
91
-
92
- // First read econet station ID (at address 0x0E)
93
- customCmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, 0x0e, 0);
94
- customCmos.writeControl(PORT_B_ENABLE, 0x0e, 0);
95
- customCmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
96
-
97
- expect(customCmos.read()).toBe(0x42);
98
-
99
- // Then read FS ID (at address 0x0F)
100
- customCmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, 0x0f, 0);
101
- customCmos.writeControl(PORT_B_ENABLE, 0x0f, 0);
102
- customCmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
103
-
104
- expect(customCmos.read()).toBe(254);
105
- });
106
- });
107
-
108
- describe("Reading and Writing non-RTC data", () => {
109
- it("should return 0xFF when CMOS is disabled", () => {
110
- // Don't enable CMOS (no PORT_B_ENABLE bit)
111
- expect(cmos.read()).toBe(0xff);
112
- });
113
-
114
- it("should write and read from CMOS memory locations", () => {
115
- // Set address to CONFIG_1 (addr 12)
116
- cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_1, 0);
117
- cmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_1, 0);
118
-
119
- // Write value 0x42 to CONFIG_1
120
- cmos.writeControl(PORT_B_ENABLE, 0x42, IC32_DATA_SEL);
121
- cmos.writeControl(PORT_B_ENABLE, 0x42, 0);
122
-
123
- // Read it back
124
- cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
125
- expect(cmos.read()).toBe(0x42);
126
-
127
- // Check persistence was called
128
- expect(mockPersistence.save).toHaveBeenCalled();
129
- });
130
-
131
- it("should only read when properly configured", () => {
132
- // Set address to CONFIG_2 (different than other tests)
133
- cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_2, 0);
134
- cmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_2, 0);
135
-
136
- // Write a known test value
137
- cmos.writeControl(PORT_B_ENABLE, 0x42, IC32_DATA_SEL);
138
- cmos.writeControl(PORT_B_ENABLE, 0x42, 0);
139
-
140
- // Without setting the read mode, should return 0xFF
141
- expect(cmos.read()).toBe(0xff);
142
-
143
- // With address select high, should return 0xFF
144
- cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, 0, IC32_READ);
145
- expect(cmos.read()).toBe(0xff);
146
-
147
- // With data select low, should return 0xFF
148
- cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ);
149
- expect(cmos.read()).toBe(0xff);
150
-
151
- // Make sure we're still pointing at the right address
152
- cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_2, 0);
153
- cmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_2, 0);
154
-
155
- // With everything set correctly, should return the value
156
- cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
157
- expect(cmos.read()).toBe(0x42);
158
- });
159
- });
160
-
161
- describe("Reading RTC values", () => {
162
- // Helper function to read a specific RTC register
163
- function readRtcRegister(register) {
164
- // Set address
165
- cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, register, 0);
166
- cmos.writeControl(PORT_B_ENABLE, register, 0);
167
-
168
- // Configure for reading
169
- cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
170
-
171
- return cmos.read();
172
- }
173
-
174
- it("should read current time from RTC registers", () => {
175
- // Helper function for BCD conversion (same as in cmos.js)
176
- function toBcd(value) {
177
- return parseInt(value.toString(10), 16);
178
- }
179
-
180
- // Test all RTC components
181
- expect(readRtcRegister(CMOS_ADDR.SECONDS)).toBe(toBcd(TEST_DATE.getSeconds()));
182
- expect(readRtcRegister(CMOS_ADDR.MINUTES)).toBe(toBcd(TEST_DATE.getMinutes()));
183
- expect(readRtcRegister(CMOS_ADDR.HOURS)).toBe(toBcd(TEST_DATE.getHours()));
184
- expect(readRtcRegister(CMOS_ADDR.DAY_OF_WEEK)).toBe(toBcd(TEST_DATE.getDay() + 1));
185
- expect(readRtcRegister(CMOS_ADDR.DAY_OF_MONTH)).toBe(toBcd(TEST_DATE.getDate()));
186
- expect(readRtcRegister(CMOS_ADDR.MONTH)).toBe(toBcd(TEST_DATE.getMonth() + 1));
187
- });
188
- });
189
-
190
- describe("Setting RTC values", () => {
191
- // Helper to read a specific RTC register
192
- function readRtcRegister(register) {
193
- cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, register, 0);
194
- cmos.writeControl(PORT_B_ENABLE, register, 0);
195
- cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
196
- return cmos.read();
197
- }
198
-
199
- // Helper to write to a specific RTC register
200
- function writeRtcRegister(register, value) {
201
- cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, register, 0);
202
- cmos.writeControl(PORT_B_ENABLE, register, 0);
203
- cmos.writeControl(PORT_B_ENABLE, value, IC32_DATA_SEL);
204
- cmos.writeControl(PORT_B_ENABLE, value, 0);
205
- }
206
-
207
- it("should update RTC values when written", () => {
208
- // Set hours to 10
209
- writeRtcRegister(CMOS_ADDR.HOURS, 0x10);
210
-
211
- // Advance time slightly to ensure changes take effect
212
- vi.advanceTimersByTime(100);
213
-
214
- // Read back hours
215
- expect(readRtcRegister(CMOS_ADDR.HOURS)).toBe(0x10);
216
-
217
- // Set minutes to 45
218
- writeRtcRegister(CMOS_ADDR.MINUTES, 0x45);
219
-
220
- // Advance time slightly
221
- vi.advanceTimersByTime(100);
222
-
223
- // Read back minutes
224
- expect(readRtcRegister(CMOS_ADDR.MINUTES)).toBe(0x45);
225
- });
226
- });
227
-
228
- describe("BCD Conversion Logic", () => {
229
- it("should correctly convert between decimal and BCD", () => {
230
- // Helper functions for BCD conversion (same as in cmos.js)
231
- const toBcd = (value) => parseInt(value.toString(10), 16);
232
- const fromBcd = (value) => parseInt(value.toString(16), 10);
233
-
234
- // Test toBcd conversion
235
- expect(toBcd(0)).toBe(0x00);
236
- expect(toBcd(9)).toBe(0x09);
237
- expect(toBcd(10)).toBe(0x10);
238
- expect(toBcd(42)).toBe(0x42);
239
- expect(toBcd(99)).toBe(0x99);
240
-
241
- // Test fromBcd conversion
242
- expect(fromBcd(0x00)).toBe(0);
243
- expect(fromBcd(0x09)).toBe(9);
244
- expect(fromBcd(0x10)).toBe(10);
245
- expect(fromBcd(0x42)).toBe(42);
246
- expect(fromBcd(0x99)).toBe(99);
247
-
248
- // Test round-trips
249
- for (let i = 0; i < 100; i++) {
250
- expect(fromBcd(toBcd(i))).toBe(i);
251
- }
252
- });
253
-
254
- it("should handle year century threshold correctly", () => {
255
- const fromBcd = (value) => parseInt(value.toString(16), 10);
256
-
257
- // Years 80-99 should use 1900 as base
258
- expect(fromBcd(0x80) >= 80 ? 1900 : 2000).toBe(1900);
259
- expect(fromBcd(0x99) >= 80 ? 1900 : 2000).toBe(1900);
260
-
261
- // Years 00-79 should use 2000 as base
262
- expect(fromBcd(0x00) >= 80 ? 1900 : 2000).toBe(2000);
263
- expect(fromBcd(0x79) >= 80 ? 1900 : 2000).toBe(2000);
264
- });
265
- });
266
- });
@@ -1,85 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
-
3
- import { Disc, IbmDiscFormat } from "../../src/disc.js";
4
- import { DiscDrive } from "../../src/disc-drive.js";
5
- import { Scheduler } from "../../src/scheduler.js";
6
-
7
- describe("Disc drive tests", function () {
8
- it("starts empty", () => {
9
- const scheduler = new Scheduler();
10
- const drive = new DiscDrive(0, scheduler);
11
- expect(drive.trackLength).toBe(IbmDiscFormat.bytesPerTrack);
12
- expect(drive.disc).toBeFalsy();
13
- expect(drive.spinning).toBe(false);
14
- drive.setPulsesCallback(() => {
15
- expect.fail("no callbacks expected");
16
- });
17
- scheduler.polltime(1000000);
18
- });
19
- it("sets a disc", () => {
20
- const scheduler = new Scheduler();
21
- const drive = new DiscDrive(0, scheduler);
22
- const disc = Disc.createBlank();
23
- drive.setDisc(disc);
24
- expect(drive.disc).toBe(disc);
25
- });
26
- it("calls back with pulses after spinning starts", () => {
27
- const scheduler = new Scheduler();
28
- const drive = new DiscDrive(0, scheduler);
29
- drive.setDisc(0, Disc.createBlank());
30
- drive.setPulsesCallback(() => {
31
- expect.fail("no callbacks expected");
32
- });
33
- scheduler.polltime(1000000);
34
- drive.startSpinning();
35
- let numPulses = 0;
36
- drive.setPulsesCallback(() => numPulses++);
37
- scheduler.polltime(500);
38
- expect(numPulses).toBe(4);
39
- drive.stopSpinning();
40
- drive.setPulsesCallback(() => {
41
- expect.fail("no callbacks expected");
42
- });
43
- scheduler.polltime(1000000);
44
- });
45
- it("generates quasi random pulses with a blank disc", () => {
46
- const scheduler = new Scheduler();
47
- const drive = new DiscDrive(0, scheduler);
48
- drive.setDisc(Disc.createBlank());
49
- drive.getQuasiRandomPulses = () => {
50
- return 0xdeadbeef;
51
- };
52
- let called = false;
53
- drive.setPulsesCallback((pulses, numPulses) => {
54
- called = true;
55
- expect(numPulses).toBe(32);
56
- expect(pulses).toBe(0xdeadbeef);
57
- });
58
- drive.startSpinning();
59
- scheduler.polltime(1000000);
60
- expect(called).toBe(true);
61
- });
62
- it("asserts index all the time with no disc", () => {
63
- const scheduler = new Scheduler();
64
- const drive = new DiscDrive(0, scheduler);
65
- expect(drive.indexPulse).toBe(true);
66
- });
67
- it("asserts index periodically with a spinning disc", () => {
68
- const scheduler = new Scheduler();
69
- const drive = new DiscDrive(0, scheduler);
70
- drive.setDisc(Disc.createBlank());
71
- drive.startSpinning();
72
- let previousIndex = drive.indexPulse;
73
- let risingEdges = 0;
74
- const cyclesPerSecond = 2 * 1000 * 1000;
75
- const cyclesPerIter = cyclesPerSecond / 60;
76
- const rpm = 300;
77
- const testSeconds = 5;
78
- for (let cycle = 0; cycle < testSeconds * cyclesPerSecond; cycle += cyclesPerIter) {
79
- scheduler.polltime(cyclesPerIter);
80
- if (drive.indexPulse && !previousIndex) risingEdges++;
81
- previousIndex = drive.indexPulse;
82
- }
83
- expect(risingEdges).toBe((rpm / 60) * testSeconds);
84
- });
85
- });
@@ -1,347 +0,0 @@
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
- });