web-a2e 1.0.0

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 (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,159 @@
1
+ import { readFileSync } from 'fs';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
4
+ import { createContext, runInContext } from 'vm';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ async function loadWasmModule() {
10
+ const wasmJsPath = join(__dirname, '..', 'public', 'a2e.js');
11
+ const wasmDir = dirname(wasmJsPath);
12
+ const code = readFileSync(wasmJsPath, 'utf8');
13
+ const context = {
14
+ module: { exports: {} },
15
+ exports: {},
16
+ globalThis: global,
17
+ console: console,
18
+ require: (await import('module')).createRequire(import.meta.url),
19
+ __dirname: wasmDir,
20
+ __filename: wasmJsPath,
21
+ process: process,
22
+ WebAssembly: WebAssembly,
23
+ URL: URL,
24
+ TextDecoder: TextDecoder,
25
+ setTimeout: setTimeout,
26
+ clearTimeout: clearTimeout,
27
+ performance: { now: () => Date.now() }
28
+ };
29
+ createContext(context);
30
+ runInContext(code, context);
31
+ return context.module.exports;
32
+ }
33
+
34
+ async function runTest() {
35
+ const createA2EModule = await loadWasmModule();
36
+ const Module = await createA2EModule();
37
+ Module._init();
38
+
39
+ const diskPath = join(__dirname, '..', 'public', 'Apple DOS 3.3 August 1980.dsk');
40
+ const diskData = readFileSync(diskPath);
41
+ const diskPtr = Module._malloc(diskData.length);
42
+ Module.HEAPU8.set(diskData, diskPtr);
43
+ const filename = 'test.dsk';
44
+ const filenamePtr = Module._malloc(filename.length + 1);
45
+ Module.stringToUTF8(filename, filenamePtr, filename.length + 1);
46
+ Module._insertDisk(0, diskPtr, diskData.length, filenamePtr);
47
+ Module._free(filenamePtr);
48
+
49
+ console.log('=== PHASE/HEAD MOVEMENT TEST ===\n');
50
+
51
+ // Manually test phase switching by writing to soft switches
52
+ // Slot 6 soft switches: $C0E0-$C0EF
53
+ // Phase 0: $C0E0 (off), $C0E1 (on)
54
+ // Phase 1: $C0E2 (off), $C0E3 (on)
55
+ // Phase 2: $C0E4 (off), $C0E5 (on)
56
+ // Phase 3: $C0E6 (off), $C0E7 (on)
57
+ // Motor: $C0E8 (off), $C0E9 (on)
58
+
59
+ const getTrack = () => Module._getDiskTrack(0);
60
+ const getQuarterTrack = () => Module._getDiskHeadPosition(0);
61
+ const getPhaseStates = () => Module._getDiskPhase(0);
62
+
63
+ console.log('Initial state:');
64
+ console.log(' Track: ' + getTrack() + ', Quarter-track: ' + getQuarterTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
65
+
66
+ // Turn on motor
67
+ Module._readMemory(0xC0E9); // Motor on
68
+ Module._runCycles(100);
69
+
70
+ console.log('\nAfter motor on:');
71
+ console.log(' Motor: ' + Module._getDiskMotorOn(0));
72
+
73
+ // Test stepping from track 0 to track 1
74
+ // Starting at track 0 (quarter-track 0), phase 0 should be on
75
+ // To move to track 1 (quarter-track 4):
76
+ // Phase 0 -> Phase 1 (+2 QT) -> Phase 2 (+2 QT) = quarter-track 4
77
+
78
+ console.log('\n=== Testing step from track 0 to track 1 ===');
79
+ console.log('Expected sequence: Phase 0 -> 1 -> 2 (quarter-tracks 0 -> 2 -> 4)');
80
+
81
+ // Make sure we start at track 0
82
+ Module._readMemory(0xC0E1); // Phase 0 on
83
+ Module._runCycles(10);
84
+ console.log('\nPhase 0 on:');
85
+ console.log(' Quarter-track: ' + getQuarterTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
86
+
87
+ // Step to phase 1
88
+ Module._readMemory(0xC0E0); // Phase 0 off
89
+ Module._readMemory(0xC0E3); // Phase 1 on
90
+ Module._runCycles(10);
91
+ console.log('\nPhase 1 on (phase 0 off):');
92
+ console.log(' Quarter-track: ' + getQuarterTrack() + ', Track: ' + getTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
93
+
94
+ // Step to phase 2
95
+ Module._readMemory(0xC0E2); // Phase 1 off
96
+ Module._readMemory(0xC0E5); // Phase 2 on
97
+ Module._runCycles(10);
98
+ console.log('\nPhase 2 on (phase 1 off):');
99
+ console.log(' Quarter-track: ' + getQuarterTrack() + ', Track: ' + getTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
100
+
101
+ // Should now be at track 1 (quarter-track 4)
102
+ console.log('\n=== Expected: Quarter-track 4, Track 1 ===');
103
+
104
+ // Test stepping back to track 0
105
+ console.log('\n=== Testing step from track 1 back to track 0 ===');
106
+ console.log('Expected sequence: Phase 2 -> 1 -> 0 (quarter-tracks 4 -> 2 -> 0)');
107
+
108
+ // Step to phase 1
109
+ Module._readMemory(0xC0E4); // Phase 2 off
110
+ Module._readMemory(0xC0E3); // Phase 1 on
111
+ Module._runCycles(10);
112
+ console.log('\nPhase 1 on (phase 2 off):');
113
+ console.log(' Quarter-track: ' + getQuarterTrack() + ', Track: ' + getTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
114
+
115
+ // Step to phase 0
116
+ Module._readMemory(0xC0E2); // Phase 1 off
117
+ Module._readMemory(0xC0E1); // Phase 0 on
118
+ Module._runCycles(10);
119
+ console.log('\nPhase 0 on (phase 1 off):');
120
+ console.log(' Quarter-track: ' + getQuarterTrack() + ', Track: ' + getTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
121
+
122
+ console.log('\n=== Expected: Quarter-track 0, Track 0 ===');
123
+
124
+ // Now test multiple track stepping
125
+ console.log('\n=== Testing multiple track step (track 0 -> track 5) ===');
126
+
127
+ // Reset to track 0
128
+ Module._readMemory(0xC0E1); // Phase 0 on
129
+ Module._runCycles(10);
130
+
131
+ for (let targetTrack = 1; targetTrack <= 5; targetTrack++) {
132
+ // Step through phases: 0 -> 1 -> 2 -> 3 -> 0 (repeats)
133
+ const phases = [0xC0E1, 0xC0E3, 0xC0E5, 0xC0E7]; // Phase on addresses
134
+ const phaseOff = [0xC0E0, 0xC0E2, 0xC0E4, 0xC0E6]; // Phase off addresses
135
+
136
+ // Calculate current phase from quarter-track
137
+ const qt = getQuarterTrack();
138
+ const currentPhase = (qt / 2) % 4;
139
+ const nextPhase = (currentPhase + 1) % 4;
140
+
141
+ // Turn off current phase
142
+ Module._readMemory(phaseOff[currentPhase]);
143
+ // Turn on next phase
144
+ Module._readMemory(phases[nextPhase]);
145
+ Module._runCycles(10);
146
+
147
+ // Step again to complete the track
148
+ const phase2 = (nextPhase + 1) % 4;
149
+ Module._readMemory(phaseOff[nextPhase]);
150
+ Module._readMemory(phases[phase2]);
151
+ Module._runCycles(10);
152
+
153
+ console.log('After stepping to track ' + targetTrack + ': Quarter-track=' + getQuarterTrack() + ', Track=' + getTrack());
154
+ }
155
+
156
+ Module._free(diskPtr);
157
+ }
158
+
159
+ runTest().catch(err => { console.error(err); process.exit(1); });
@@ -0,0 +1,291 @@
1
+ /*
2
+ * test_emulator.cpp - Integration tests for the Emulator class
3
+ *
4
+ * Tests the full emulator coordinator including initialization, reset,
5
+ * execution, memory access, video, beam position, soft switches,
6
+ * slot management, screen text, disassembly, and speed control.
7
+ */
8
+
9
+ #define CATCH_CONFIG_MAIN
10
+ #include "catch.hpp"
11
+
12
+ #include "emulator.hpp"
13
+
14
+ using namespace a2e;
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Initialization
18
+ // ---------------------------------------------------------------------------
19
+
20
+ TEST_CASE("Emulator init does not crash", "[emulator][init]") {
21
+ Emulator emu;
22
+ REQUIRE_NOTHROW(emu.init());
23
+ }
24
+
25
+ TEST_CASE("Emulator PC points to reset vector destination after init", "[emulator][init]") {
26
+ Emulator emu;
27
+ emu.init();
28
+
29
+ // After reset, the 65C02 reads the reset vector at $FFFC/$FFFD and sets
30
+ // PC to that address. The Apple IIe ROM reset vector points into the
31
+ // $FA00-$FFFF range (monitor / reset handler).
32
+ uint16_t pc = emu.getPC();
33
+ REQUIRE(pc >= 0xC000);
34
+ REQUIRE(pc <= 0xFFFF);
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Reset
39
+ // ---------------------------------------------------------------------------
40
+
41
+ TEST_CASE("Emulator reset returns PC to reset vector", "[emulator][reset]") {
42
+ Emulator emu;
43
+ emu.init();
44
+
45
+ uint16_t pcAfterInit = emu.getPC();
46
+
47
+ // Run some cycles to move PC away from the reset address
48
+ emu.runCycles(1000);
49
+ REQUIRE(emu.getPC() != pcAfterInit);
50
+
51
+ // Reset should return PC to the same reset vector destination
52
+ emu.reset();
53
+ REQUIRE(emu.getPC() == pcAfterInit);
54
+ }
55
+
56
+ TEST_CASE("Emulator warmReset preserves memory but resets PC", "[emulator][reset]") {
57
+ Emulator emu;
58
+ emu.init();
59
+
60
+ uint16_t pcAfterInit = emu.getPC();
61
+
62
+ // Write a known value into low RAM
63
+ emu.writeMemory(0x0300, 0xAB);
64
+ REQUIRE(emu.readMemory(0x0300) == 0xAB);
65
+
66
+ // Run some cycles to move PC
67
+ emu.runCycles(1000);
68
+
69
+ // Warm reset: memory preserved, PC returns to reset vector
70
+ emu.warmReset();
71
+ REQUIRE(emu.getPC() == pcAfterInit);
72
+ REQUIRE(emu.readMemory(0x0300) == 0xAB);
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Execution
77
+ // ---------------------------------------------------------------------------
78
+
79
+ TEST_CASE("Emulator runCycles advances cycles and PC", "[emulator][execution]") {
80
+ Emulator emu;
81
+ emu.init();
82
+
83
+ uint16_t pcBefore = emu.getPC();
84
+ uint64_t cyclesBefore = emu.getTotalCycles();
85
+
86
+ emu.runCycles(100);
87
+
88
+ REQUIRE(emu.getTotalCycles() > cyclesBefore);
89
+ REQUIRE(emu.getPC() != pcBefore);
90
+ }
91
+
92
+ TEST_CASE("Emulator stepInstruction executes one instruction", "[emulator][execution]") {
93
+ Emulator emu;
94
+ emu.init();
95
+
96
+ uint64_t cyclesBefore = emu.getTotalCycles();
97
+ uint16_t pcBefore = emu.getPC();
98
+
99
+ emu.stepInstruction();
100
+
101
+ // At least 2 cycles for the shortest 65C02 instruction, PC should have moved
102
+ REQUIRE(emu.getTotalCycles() > cyclesBefore);
103
+ REQUIRE(emu.getTotalCycles() <= cyclesBefore + 7); // max 7 cycles for any instruction
104
+ REQUIRE(emu.getPC() != pcBefore);
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Pause
109
+ // ---------------------------------------------------------------------------
110
+
111
+ TEST_CASE("Emulator setPaused and isPaused", "[emulator][pause]") {
112
+ Emulator emu;
113
+ emu.init();
114
+
115
+ REQUIRE_FALSE(emu.isPaused());
116
+
117
+ emu.setPaused(true);
118
+ REQUIRE(emu.isPaused());
119
+
120
+ // runCycles should not advance when paused
121
+ uint64_t cyclesBefore = emu.getTotalCycles();
122
+ emu.runCycles(1000);
123
+ REQUIRE(emu.getTotalCycles() == cyclesBefore);
124
+
125
+ emu.setPaused(false);
126
+ REQUIRE_FALSE(emu.isPaused());
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // Memory access
131
+ // ---------------------------------------------------------------------------
132
+
133
+ TEST_CASE("Emulator writeMemory and readMemory round-trip", "[emulator][memory]") {
134
+ Emulator emu;
135
+ emu.init();
136
+
137
+ emu.writeMemory(0x0400, 0x42);
138
+ REQUIRE(emu.readMemory(0x0400) == 0x42);
139
+ }
140
+
141
+ TEST_CASE("Emulator peekMemory reads same as readMemory for normal RAM", "[emulator][memory]") {
142
+ Emulator emu;
143
+ emu.init();
144
+
145
+ emu.writeMemory(0x0400, 0x55);
146
+ REQUIRE(emu.peekMemory(0x0400) == emu.readMemory(0x0400));
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Video / Framebuffer
151
+ // ---------------------------------------------------------------------------
152
+
153
+ TEST_CASE("Emulator getFramebuffer returns non-null after init", "[emulator][video]") {
154
+ Emulator emu;
155
+ emu.init();
156
+
157
+ const uint8_t* fb = emu.getFramebuffer();
158
+ REQUIRE(fb != nullptr);
159
+ }
160
+
161
+ TEST_CASE("Emulator getFramebufferSize equals expected RGBA buffer size", "[emulator][video]") {
162
+ Emulator emu;
163
+ emu.init();
164
+
165
+ // 560 * 384 * 4 (RGBA) = 860160
166
+ REQUIRE(emu.getFramebufferSize() == 860160);
167
+ }
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Beam position
171
+ // ---------------------------------------------------------------------------
172
+
173
+ TEST_CASE("Emulator beam position returns valid scanline and hPos", "[emulator][beam]") {
174
+ Emulator emu;
175
+ emu.init();
176
+
177
+ // Run a few cycles so beam position is somewhere meaningful
178
+ emu.runCycles(200);
179
+
180
+ int scanline = emu.getBeamScanline();
181
+ int hPos = emu.getBeamHPos();
182
+
183
+ REQUIRE(scanline >= 0);
184
+ REQUIRE(scanline < 262); // 262 scanlines per NTSC frame
185
+ REQUIRE(hPos >= 0);
186
+ REQUIRE(hPos < 65); // 65 cycles per scanline
187
+ }
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Soft switches
191
+ // ---------------------------------------------------------------------------
192
+
193
+ TEST_CASE("Emulator getSoftSwitchState returns packed state", "[emulator][softswitch]") {
194
+ Emulator emu;
195
+ emu.init();
196
+
197
+ // getSoftSwitchState returns a 64-bit packed value; just verify it is callable
198
+ // and produces a reasonable value (all zeros except TEXT mode which is set by default)
199
+ uint64_t state = emu.getSoftSwitchState();
200
+ // TEXT mode bit (bit 0) should be set on a fresh init
201
+ REQUIRE((state & 0x01) == 0x01);
202
+ }
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // Slot management
206
+ // ---------------------------------------------------------------------------
207
+
208
+ TEST_CASE("Emulator slot management: isSlotEmpty and getSlotCardName", "[emulator][slots]") {
209
+ Emulator emu;
210
+ emu.init();
211
+
212
+ SECTION("Slot 3 is never empty (built-in 80-column)") {
213
+ REQUIRE_FALSE(emu.isSlotEmpty(3));
214
+ REQUIRE(std::string(emu.getSlotCardName(3)) == "80col");
215
+ }
216
+
217
+ SECTION("Slot 6 has Disk II by default") {
218
+ REQUIRE_FALSE(emu.isSlotEmpty(6));
219
+ REQUIRE(std::string(emu.getSlotCardName(6)) == "disk2");
220
+ }
221
+
222
+ SECTION("Slot 4 has Mockingboard by default") {
223
+ REQUIRE_FALSE(emu.isSlotEmpty(4));
224
+ REQUIRE(std::string(emu.getSlotCardName(4)) == "mockingboard");
225
+ }
226
+
227
+ SECTION("Slot 1 is empty by default") {
228
+ REQUIRE(emu.isSlotEmpty(1));
229
+ }
230
+ }
231
+
232
+ // ---------------------------------------------------------------------------
233
+ // Screen text
234
+ // ---------------------------------------------------------------------------
235
+
236
+ TEST_CASE("Emulator readScreenText returns a string", "[emulator][screen]") {
237
+ Emulator emu;
238
+ emu.init();
239
+
240
+ // Read full screen (24 rows x 40 cols)
241
+ const char* text = emu.readScreenText(0, 0, 23, 39);
242
+ REQUIRE(text != nullptr);
243
+ }
244
+
245
+ // ---------------------------------------------------------------------------
246
+ // Disassembly
247
+ // ---------------------------------------------------------------------------
248
+
249
+ TEST_CASE("Emulator disassembleAt returns non-empty string", "[emulator][disasm]") {
250
+ Emulator emu;
251
+ emu.init();
252
+
253
+ // Disassemble at current PC (should be in ROM after reset)
254
+ const char* disasm = emu.disassembleAt(emu.getPC());
255
+ REQUIRE(disasm != nullptr);
256
+ REQUIRE(std::string(disasm).length() > 0);
257
+ }
258
+
259
+ // ---------------------------------------------------------------------------
260
+ // Speed control
261
+ // ---------------------------------------------------------------------------
262
+
263
+ TEST_CASE("Emulator speed multiplier set and get", "[emulator][speed]") {
264
+ Emulator emu;
265
+ emu.init();
266
+
267
+ REQUIRE(emu.getSpeedMultiplier() == 1);
268
+
269
+ emu.setSpeedMultiplier(2);
270
+ REQUIRE(emu.getSpeedMultiplier() == 2);
271
+
272
+ emu.setSpeedMultiplier(8);
273
+ REQUIRE(emu.getSpeedMultiplier() == 8);
274
+
275
+ // Values are clamped to 1-8
276
+ emu.setSpeedMultiplier(0);
277
+ REQUIRE(emu.getSpeedMultiplier() == 1);
278
+
279
+ emu.setSpeedMultiplier(100);
280
+ REQUIRE(emu.getSpeedMultiplier() == 8);
281
+ }
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // screenCodeToAscii
285
+ // ---------------------------------------------------------------------------
286
+
287
+ TEST_CASE("Emulator screenCodeToAscii converts known codes", "[emulator][screen]") {
288
+ // Normal ASCII 'A' = 0xC1 in Apple II screen memory
289
+ int result = Emulator::screenCodeToAscii(0xC1);
290
+ REQUIRE(result == 'A');
291
+ }
@@ -0,0 +1,91 @@
1
+ /*
2
+ * test_emulator_basic.cpp - Integration tests for Emulator BASIC debugging
3
+ *
4
+ * Tests BASIC program tracking, BASIC breakpoints, BASIC error state,
5
+ * and related BASIC debugging API through the Emulator facade.
6
+ */
7
+
8
+ #define CATCH_CONFIG_MAIN
9
+ #include "catch.hpp"
10
+
11
+ #include "emulator.hpp"
12
+
13
+ using namespace a2e;
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // BASIC program running state
17
+ // ---------------------------------------------------------------------------
18
+
19
+ TEST_CASE("Emulator isBasicProgramRunning is false after init", "[emulator][basic]") {
20
+ Emulator emu;
21
+ emu.init();
22
+
23
+ REQUIRE_FALSE(emu.isBasicProgramRunning());
24
+ }
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // BASIC breakpoints
28
+ // ---------------------------------------------------------------------------
29
+
30
+ TEST_CASE("Emulator addBasicBreakpoint makes hasBasicBreakpoints true", "[emulator][basic][breakpoint]") {
31
+ Emulator emu;
32
+ emu.init();
33
+
34
+ REQUIRE_FALSE(emu.hasBasicBreakpoints());
35
+
36
+ emu.addBasicBreakpoint(10, -1); // Line 10, whole line
37
+ REQUIRE(emu.hasBasicBreakpoints());
38
+ }
39
+
40
+ TEST_CASE("Emulator clearBasicBreakpoints removes all BASIC breakpoints", "[emulator][basic][breakpoint]") {
41
+ Emulator emu;
42
+ emu.init();
43
+
44
+ emu.addBasicBreakpoint(10, -1);
45
+ emu.addBasicBreakpoint(20, 0);
46
+ REQUIRE(emu.hasBasicBreakpoints());
47
+
48
+ emu.clearBasicBreakpoints();
49
+ REQUIRE_FALSE(emu.hasBasicBreakpoints());
50
+ }
51
+
52
+ TEST_CASE("Emulator removeBasicBreakpoint removes specific breakpoint", "[emulator][basic][breakpoint]") {
53
+ Emulator emu;
54
+ emu.init();
55
+
56
+ emu.addBasicBreakpoint(10, -1);
57
+ emu.removeBasicBreakpoint(10, -1);
58
+
59
+ REQUIRE_FALSE(emu.hasBasicBreakpoints());
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // BASIC breakpoint hit state
64
+ // ---------------------------------------------------------------------------
65
+
66
+ TEST_CASE("Emulator isBasicBreakpointHit is initially false", "[emulator][basic][breakpoint]") {
67
+ Emulator emu;
68
+ emu.init();
69
+
70
+ REQUIRE_FALSE(emu.isBasicBreakpointHit());
71
+ }
72
+
73
+ TEST_CASE("Emulator clearBasicBreakpointHit clears hit state", "[emulator][basic][breakpoint]") {
74
+ Emulator emu;
75
+ emu.init();
76
+
77
+ // Hit state should already be false; clearing it should not crash
78
+ emu.clearBasicBreakpointHit();
79
+ REQUIRE_FALSE(emu.isBasicBreakpointHit());
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // BASIC error state
84
+ // ---------------------------------------------------------------------------
85
+
86
+ TEST_CASE("Emulator isBasicErrorHit is initially false", "[emulator][basic][error]") {
87
+ Emulator emu;
88
+ emu.init();
89
+
90
+ REQUIRE_FALSE(emu.isBasicErrorHit());
91
+ }