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,344 @@
1
+ /*
2
+ * test_emulator_debug.cpp - Integration tests for Emulator debug features
3
+ *
4
+ * Tests breakpoints, watchpoints, beam breakpoints, trace logging,
5
+ * cycle profiling, step over, step out, and related debug state.
6
+ */
7
+
8
+ #define CATCH_CONFIG_MAIN
9
+ #include "catch.hpp"
10
+
11
+ #include "emulator.hpp"
12
+
13
+ #include <memory>
14
+
15
+ using namespace a2e;
16
+
17
+ // Helper: create an initialized emulator ready for testing
18
+ static std::unique_ptr<Emulator> makeEmulator() {
19
+ auto emu = std::make_unique<Emulator>();
20
+ emu->init();
21
+ return emu;
22
+ }
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Breakpoints
26
+ // ---------------------------------------------------------------------------
27
+
28
+ TEST_CASE("Breakpoint triggers when PC reaches address", "[emulator][debug][breakpoint]") {
29
+ Emulator emu;
30
+ emu.init();
31
+
32
+ // Write a JMP $0400 loop at $0400: 4C 00 04
33
+ emu.writeMemory(0x0400, 0x4C); // JMP
34
+ emu.writeMemory(0x0401, 0x00); // low byte
35
+ emu.writeMemory(0x0402, 0x04); // high byte
36
+
37
+ // Set PC to $0400 and add breakpoint there
38
+ emu.setPC(0x0400);
39
+ emu.addBreakpoint(0x0400);
40
+
41
+ // Need to unpause since breakpoints pause on hit
42
+ // The first execution should immediately hit the breakpoint
43
+ emu.runCycles(100);
44
+
45
+ REQUIRE(emu.isBreakpointHit());
46
+ REQUIRE(emu.getBreakpointAddress() == 0x0400);
47
+ }
48
+
49
+ TEST_CASE("removeBreakpoint prevents breakpoint from triggering", "[emulator][debug][breakpoint]") {
50
+ Emulator emu;
51
+ emu.init();
52
+
53
+ // Write NOP sled at $0400
54
+ for (int i = 0; i < 16; i++) {
55
+ emu.writeMemory(0x0400 + i, 0xEA); // NOP
56
+ }
57
+ // End with JMP $0400
58
+ emu.writeMemory(0x0410, 0x4C);
59
+ emu.writeMemory(0x0411, 0x00);
60
+ emu.writeMemory(0x0412, 0x04);
61
+
62
+ emu.setPC(0x0400);
63
+ emu.addBreakpoint(0x0408);
64
+
65
+ // Run - breakpoint should hit at $0408
66
+ emu.runCycles(100);
67
+ REQUIRE(emu.isBreakpointHit());
68
+ REQUIRE(emu.getBreakpointAddress() == 0x0408);
69
+
70
+ // Remove it and resume
71
+ emu.removeBreakpoint(0x0408);
72
+ emu.setPaused(false);
73
+ emu.runCycles(1000);
74
+
75
+ // Should NOT hit a breakpoint now (will just loop)
76
+ REQUIRE_FALSE(emu.isBreakpointHit());
77
+ }
78
+
79
+ TEST_CASE("enableBreakpoint(false) prevents breakpoint from triggering", "[emulator][debug][breakpoint]") {
80
+ Emulator emu;
81
+ emu.init();
82
+
83
+ // NOP sled + JMP loop
84
+ for (int i = 0; i < 16; i++) {
85
+ emu.writeMemory(0x0400 + i, 0xEA);
86
+ }
87
+ emu.writeMemory(0x0410, 0x4C);
88
+ emu.writeMemory(0x0411, 0x00);
89
+ emu.writeMemory(0x0412, 0x04);
90
+
91
+ emu.setPC(0x0400);
92
+ emu.addBreakpoint(0x0408);
93
+ emu.enableBreakpoint(0x0408, false); // disable it
94
+
95
+ emu.runCycles(1000);
96
+ REQUIRE_FALSE(emu.isBreakpointHit());
97
+ }
98
+
99
+ TEST_CASE("getBreakpointAddress returns correct address on hit", "[emulator][debug][breakpoint]") {
100
+ Emulator emu;
101
+ emu.init();
102
+
103
+ emu.writeMemory(0x0500, 0x4C);
104
+ emu.writeMemory(0x0501, 0x00);
105
+ emu.writeMemory(0x0502, 0x05);
106
+
107
+ emu.setPC(0x0500);
108
+ emu.addBreakpoint(0x0500);
109
+ emu.runCycles(100);
110
+
111
+ REQUIRE(emu.isBreakpointHit());
112
+ REQUIRE(emu.getBreakpointAddress() == 0x0500);
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Watchpoints
117
+ // ---------------------------------------------------------------------------
118
+
119
+ TEST_CASE("Watchpoint triggers on write to watched address", "[emulator][debug][watchpoint]") {
120
+ Emulator emu;
121
+ emu.init();
122
+
123
+ // Add a write watchpoint on $0400
124
+ emu.addWatchpoint(0x0400, 0x0400, Emulator::WP_WRITE);
125
+
126
+ // Write a small program at $0300 that writes to $0400 then loops:
127
+ // LDA #$42 ; A9 42
128
+ // STA $0400 ; 8D 00 04
129
+ // JMP $0305 ; 4C 05 03
130
+ emu.writeMemory(0x0300, 0xA9);
131
+ emu.writeMemory(0x0301, 0x42);
132
+ emu.writeMemory(0x0302, 0x8D);
133
+ emu.writeMemory(0x0303, 0x00);
134
+ emu.writeMemory(0x0304, 0x04);
135
+ emu.writeMemory(0x0305, 0x4C);
136
+ emu.writeMemory(0x0306, 0x05);
137
+ emu.writeMemory(0x0307, 0x03);
138
+
139
+ emu.setPC(0x0300);
140
+ emu.runCycles(100);
141
+
142
+ REQUIRE(emu.isWatchpointHit());
143
+ REQUIRE(emu.getWatchpointAddress() == 0x0400);
144
+ }
145
+
146
+ TEST_CASE("clearWatchpoints removes all watchpoints", "[emulator][debug][watchpoint]") {
147
+ Emulator emu;
148
+ emu.init();
149
+
150
+ emu.addWatchpoint(0x0400, 0x0400, Emulator::WP_WRITE);
151
+ emu.clearWatchpoints();
152
+
153
+ // Write to $0400 via a small program
154
+ emu.writeMemory(0x0300, 0xA9); // LDA #$42
155
+ emu.writeMemory(0x0301, 0x42);
156
+ emu.writeMemory(0x0302, 0x8D); // STA $0400
157
+ emu.writeMemory(0x0303, 0x00);
158
+ emu.writeMemory(0x0304, 0x04);
159
+ emu.writeMemory(0x0305, 0x4C); // JMP $0305
160
+ emu.writeMemory(0x0306, 0x05);
161
+ emu.writeMemory(0x0307, 0x03);
162
+
163
+ emu.setPC(0x0300);
164
+ emu.runCycles(100);
165
+
166
+ REQUIRE_FALSE(emu.isWatchpointHit());
167
+ }
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Beam breakpoints
171
+ // ---------------------------------------------------------------------------
172
+
173
+ TEST_CASE("Beam breakpoint triggers at specified scanline", "[emulator][debug][beam]") {
174
+ Emulator emu;
175
+ emu.init();
176
+
177
+ // Add beam breakpoint at scanline 100, any hPos (-1 = wildcard)
178
+ int32_t id = emu.addBeamBreakpoint(100, -1);
179
+ REQUIRE(id >= 0);
180
+
181
+ // Run enough cycles for the beam to reach scanline 100
182
+ // 100 scanlines * 65 cycles/scanline = 6500 cycles minimum
183
+ emu.runCycles(17030); // One full frame
184
+
185
+ REQUIRE(emu.isBeamBreakpointHit());
186
+ }
187
+
188
+ TEST_CASE("removeBeamBreakpoint prevents beam break from triggering", "[emulator][debug][beam]") {
189
+ Emulator emu;
190
+ emu.init();
191
+
192
+ int32_t id = emu.addBeamBreakpoint(100, -1);
193
+ emu.removeBeamBreakpoint(id);
194
+
195
+ emu.runCycles(17030);
196
+
197
+ REQUIRE_FALSE(emu.isBeamBreakpointHit());
198
+ }
199
+
200
+ TEST_CASE("clearAllBeamBreakpoints clears all beam breaks", "[emulator][debug][beam]") {
201
+ Emulator emu;
202
+ emu.init();
203
+
204
+ emu.addBeamBreakpoint(50, -1);
205
+ emu.addBeamBreakpoint(100, -1);
206
+ emu.clearAllBeamBreakpoints();
207
+
208
+ emu.runCycles(17030);
209
+
210
+ REQUIRE_FALSE(emu.isBeamBreakpointHit());
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // Trace log
215
+ // ---------------------------------------------------------------------------
216
+
217
+ TEST_CASE("Trace records entries when enabled", "[emulator][debug][trace]") {
218
+ Emulator emu;
219
+ emu.init();
220
+
221
+ emu.setTraceEnabled(true);
222
+ emu.runCycles(200);
223
+
224
+ REQUIRE(emu.getTraceCount() > 0);
225
+ REQUIRE(emu.getTraceBuffer() != nullptr);
226
+ REQUIRE(emu.getTraceCapacity() > 0);
227
+ }
228
+
229
+ TEST_CASE("clearTrace resets trace count to zero", "[emulator][debug][trace]") {
230
+ Emulator emu;
231
+ emu.init();
232
+
233
+ emu.setTraceEnabled(true);
234
+ emu.runCycles(200);
235
+ REQUIRE(emu.getTraceCount() > 0);
236
+
237
+ emu.clearTrace();
238
+ REQUIRE(emu.getTraceCount() == 0);
239
+ }
240
+
241
+ // ---------------------------------------------------------------------------
242
+ // Cycle profiling
243
+ // ---------------------------------------------------------------------------
244
+
245
+ TEST_CASE("Profiling records cycles when enabled", "[emulator][debug][profile]") {
246
+ Emulator emu;
247
+ emu.init();
248
+
249
+ emu.setProfileEnabled(true);
250
+ emu.clearProfile();
251
+ emu.runCycles(1000);
252
+
253
+ // Check that at least one address has non-zero cycle counts
254
+ const uint32_t* profile = emu.getProfileCycles();
255
+ REQUIRE(profile != nullptr);
256
+
257
+ bool hasNonZero = false;
258
+ for (int i = 0; i < 65536; i++) {
259
+ if (profile[i] > 0) {
260
+ hasNonZero = true;
261
+ break;
262
+ }
263
+ }
264
+ REQUIRE(hasNonZero);
265
+ }
266
+
267
+ TEST_CASE("clearProfile resets all profile cycle counts", "[emulator][debug][profile]") {
268
+ Emulator emu;
269
+ emu.init();
270
+
271
+ emu.setProfileEnabled(true);
272
+ emu.runCycles(1000);
273
+ emu.clearProfile();
274
+
275
+ const uint32_t* profile = emu.getProfileCycles();
276
+ bool allZero = true;
277
+ for (int i = 0; i < 65536; i++) {
278
+ if (profile[i] != 0) {
279
+ allZero = false;
280
+ break;
281
+ }
282
+ }
283
+ REQUIRE(allZero);
284
+ }
285
+
286
+ // ---------------------------------------------------------------------------
287
+ // Step Over
288
+ // ---------------------------------------------------------------------------
289
+
290
+ TEST_CASE("stepOver on JSR sets temp breakpoint after JSR", "[emulator][debug][stepover]") {
291
+ Emulator emu;
292
+ emu.init();
293
+
294
+ // Write JSR $0500 at $0400
295
+ emu.writeMemory(0x0400, 0x20); // JSR
296
+ emu.writeMemory(0x0401, 0x00); // low byte
297
+ emu.writeMemory(0x0402, 0x05); // high byte
298
+ // Write NOP at $0403
299
+ emu.writeMemory(0x0403, 0xEA);
300
+
301
+ // Write RTS at $0500 so the subroutine returns
302
+ emu.writeMemory(0x0500, 0x60); // RTS
303
+
304
+ emu.setPC(0x0400);
305
+ emu.setPaused(true);
306
+
307
+ uint16_t tempBp = emu.stepOver();
308
+
309
+ // stepOver on JSR should set temp breakpoint at PC+3 = $0403
310
+ REQUIRE(tempBp == 0x0403);
311
+ }
312
+
313
+ // ---------------------------------------------------------------------------
314
+ // Step Out
315
+ // ---------------------------------------------------------------------------
316
+
317
+ TEST_CASE("stepOut sets temp breakpoint at return address from stack", "[emulator][debug][stepout]") {
318
+ Emulator emu;
319
+ emu.init();
320
+
321
+ // Simulate being inside a subroutine:
322
+ // Push a fake return address (e.g., $04FF, since RTS adds 1 -> $0500)
323
+ // onto the stack.
324
+ uint8_t sp = emu.getSP();
325
+
326
+ // Push high byte then low byte (as JSR does)
327
+ emu.writeMemory(0x0100 + sp, 0x04); // PCH
328
+ sp--;
329
+ emu.writeMemory(0x0100 + sp, 0xFF); // PCL
330
+ sp--;
331
+ emu.setSP(sp);
332
+
333
+ // Write a NOP loop at current PC
334
+ uint16_t pc = emu.getPC();
335
+ emu.writeMemory(pc, 0xEA); // NOP
336
+ emu.writeMemory(pc + 1, 0xEA); // NOP
337
+
338
+ emu.setPaused(true);
339
+
340
+ uint16_t tempBp = emu.stepOut();
341
+
342
+ // stepOut reads stack: (PCL,PCH) = ($FF,$04) -> $04FF + 1 = $0500
343
+ REQUIRE(tempBp == 0x0500);
344
+ }
@@ -0,0 +1,153 @@
1
+ /*
2
+ * test_emulator_disk.cpp - Integration tests for Emulator disk operations
3
+ *
4
+ * Tests disk insert, eject, blank disk creation, two-drive support,
5
+ * filename tracking, SmartPort, and error handling through the
6
+ * Emulator facade.
7
+ */
8
+
9
+ #define CATCH_CONFIG_MAIN
10
+ #include "catch.hpp"
11
+
12
+ #include "emulator.hpp"
13
+
14
+ #include <cstring>
15
+ #include <vector>
16
+
17
+ using namespace a2e;
18
+
19
+ // Standard DSK image size: 35 tracks * 16 sectors * 256 bytes
20
+ static constexpr size_t DSK_SIZE = 143360;
21
+
22
+ // Helper: create a valid-sized DSK image filled with a byte value
23
+ static std::vector<uint8_t> makeDskImage(uint8_t fill = 0x00) {
24
+ return std::vector<uint8_t>(DSK_SIZE, fill);
25
+ }
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Insert disk
29
+ // ---------------------------------------------------------------------------
30
+
31
+ TEST_CASE("Emulator insertDisk succeeds with valid DSK data", "[emulator][disk]") {
32
+ Emulator emu;
33
+ emu.init();
34
+
35
+ auto img = makeDskImage();
36
+ bool result = emu.insertDisk(0, img.data(), img.size(), "test.dsk");
37
+ REQUIRE(result);
38
+ }
39
+
40
+ TEST_CASE("Emulator getDiskFilename returns inserted filename", "[emulator][disk]") {
41
+ Emulator emu;
42
+ emu.init();
43
+
44
+ auto img = makeDskImage();
45
+ emu.insertDisk(0, img.data(), img.size(), "test.dsk");
46
+
47
+ const char* name = emu.getDiskFilename(0);
48
+ REQUIRE(name != nullptr);
49
+ REQUIRE(std::string(name) == "test.dsk");
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Eject disk
54
+ // ---------------------------------------------------------------------------
55
+
56
+ TEST_CASE("Emulator ejectDisk clears the drive", "[emulator][disk]") {
57
+ Emulator emu;
58
+ emu.init();
59
+
60
+ auto img = makeDskImage();
61
+ emu.insertDisk(0, img.data(), img.size(), "test.dsk");
62
+ emu.ejectDisk(0);
63
+
64
+ // After ejecting, getDiskData should return nullptr
65
+ size_t dataSize = 0;
66
+ const uint8_t* data = emu.getDiskData(0, &dataSize);
67
+ REQUIRE(data == nullptr);
68
+ }
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Blank disk
72
+ // ---------------------------------------------------------------------------
73
+
74
+ TEST_CASE("Emulator insertBlankDisk succeeds", "[emulator][disk]") {
75
+ Emulator emu;
76
+ emu.init();
77
+
78
+ bool result = emu.insertBlankDisk(0);
79
+ REQUIRE(result);
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Two drives
84
+ // ---------------------------------------------------------------------------
85
+
86
+ TEST_CASE("Emulator supports two drives simultaneously", "[emulator][disk]") {
87
+ Emulator emu;
88
+ emu.init();
89
+
90
+ auto img0 = makeDskImage(0x00);
91
+ auto img1 = makeDskImage(0xFF);
92
+
93
+ bool r0 = emu.insertDisk(0, img0.data(), img0.size(), "disk1.dsk");
94
+ bool r1 = emu.insertDisk(1, img1.data(), img1.size(), "disk2.dsk");
95
+
96
+ REQUIRE(r0);
97
+ REQUIRE(r1);
98
+
99
+ REQUIRE(std::string(emu.getDiskFilename(0)) == "disk1.dsk");
100
+ REQUIRE(std::string(emu.getDiskFilename(1)) == "disk2.dsk");
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // getDisk reference
105
+ // ---------------------------------------------------------------------------
106
+
107
+ TEST_CASE("Emulator getDisk returns a valid Disk2Card reference", "[emulator][disk]") {
108
+ Emulator emu;
109
+ emu.init();
110
+
111
+ Disk2Card& disk = emu.getDisk();
112
+ REQUIRE(std::string(disk.getName()) == "Disk II");
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // SmartPort
117
+ // ---------------------------------------------------------------------------
118
+
119
+ TEST_CASE("Emulator isSmartPortCardInstalled reflects slot configuration", "[emulator][disk][smartport]") {
120
+ Emulator emu;
121
+ emu.init();
122
+
123
+ // By default, SmartPort is not installed (no card in slot 7)
124
+ // The result depends on default configuration
125
+ // Just verify the method is callable without crashing
126
+ bool installed = emu.isSmartPortCardInstalled();
127
+
128
+ if (installed) {
129
+ // If SmartPort is installed, insertSmartPortImage should be callable
130
+ // (actual success depends on data validity)
131
+ std::vector<uint8_t> hdvData(512 * 280, 0x00); // Minimal ProDOS volume
132
+ // This may or may not succeed depending on image validation
133
+ emu.insertSmartPortImage(0, hdvData.data(), hdvData.size(), "test.hdv");
134
+ }
135
+
136
+ // Either way, no crash
137
+ REQUIRE(true);
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Invalid data
142
+ // ---------------------------------------------------------------------------
143
+
144
+ TEST_CASE("Emulator insertDisk with invalid size returns false", "[emulator][disk]") {
145
+ Emulator emu;
146
+ emu.init();
147
+
148
+ // A DSK image must be exactly 143360 bytes (or a valid NIB/WOZ size)
149
+ // An arbitrary size should be rejected
150
+ std::vector<uint8_t> badData(1000, 0x00);
151
+ bool result = emu.insertDisk(0, badData.data(), badData.size(), "bad.dsk");
152
+ REQUIRE_FALSE(result);
153
+ }
@@ -0,0 +1,163 @@
1
+ /*
2
+ * test_emulator_state.cpp - Integration tests for Emulator state serialization
3
+ *
4
+ * Tests exportState, importState, round-trip fidelity, CPU state preservation,
5
+ * and error handling for invalid data.
6
+ */
7
+
8
+ #define CATCH_CONFIG_MAIN
9
+ #include "catch.hpp"
10
+
11
+ #include "emulator.hpp"
12
+
13
+ #include <cstring>
14
+ #include <vector>
15
+
16
+ using namespace a2e;
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Export state
20
+ // ---------------------------------------------------------------------------
21
+
22
+ TEST_CASE("Emulator exportState returns non-null data with size > 0", "[emulator][state]") {
23
+ Emulator emu;
24
+ emu.init();
25
+
26
+ size_t size = 0;
27
+ const uint8_t* data = emu.exportState(&size);
28
+
29
+ REQUIRE(data != nullptr);
30
+ REQUIRE(size > 0);
31
+ }
32
+
33
+ TEST_CASE("Emulator exportState size is reasonable", "[emulator][state]") {
34
+ Emulator emu;
35
+ emu.init();
36
+
37
+ size_t size = 0;
38
+ emu.exportState(&size);
39
+
40
+ // State includes 128KB RAM (main + aux), 16KB LC RAM, CPU state,
41
+ // soft switches, disk state, etc. Should be at least 128KB.
42
+ REQUIRE(size >= 128 * 1024);
43
+ // But not unreasonably large (under 2MB)
44
+ REQUIRE(size < 2 * 1024 * 1024);
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Import state
49
+ // ---------------------------------------------------------------------------
50
+
51
+ TEST_CASE("Emulator importState accepts previously exported data", "[emulator][state]") {
52
+ Emulator emu;
53
+ emu.init();
54
+
55
+ size_t size = 0;
56
+ const uint8_t* data = emu.exportState(&size);
57
+ REQUIRE(data != nullptr);
58
+
59
+ // Copy the exported data since import may reset internal buffers
60
+ std::vector<uint8_t> stateCopy(data, data + size);
61
+
62
+ bool result = emu.importState(stateCopy.data(), stateCopy.size());
63
+ REQUIRE(result);
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Round-trip: memory preserved
68
+ // ---------------------------------------------------------------------------
69
+
70
+ TEST_CASE("Emulator state round-trip preserves memory", "[emulator][state]") {
71
+ Emulator emu;
72
+ emu.init();
73
+
74
+ // Write known values to several RAM locations
75
+ emu.writeMemory(0x0300, 0xDE);
76
+ emu.writeMemory(0x0301, 0xAD);
77
+ emu.writeMemory(0x0302, 0xBE);
78
+ emu.writeMemory(0x0303, 0xEF);
79
+
80
+ // Export
81
+ size_t size = 0;
82
+ const uint8_t* data = emu.exportState(&size);
83
+ std::vector<uint8_t> stateCopy(data, data + size);
84
+
85
+ // Reset clears memory
86
+ emu.reset();
87
+ // Verify memory was cleared (reset re-initializes)
88
+ // Note: after reset, ROM re-initializes; RAM at $0300 should be cleared
89
+ REQUIRE(emu.readMemory(0x0300) != 0xDE);
90
+
91
+ // Import previously saved state
92
+ bool result = emu.importState(stateCopy.data(), stateCopy.size());
93
+ REQUIRE(result);
94
+
95
+ // Memory should be restored
96
+ REQUIRE(emu.readMemory(0x0300) == 0xDE);
97
+ REQUIRE(emu.readMemory(0x0301) == 0xAD);
98
+ REQUIRE(emu.readMemory(0x0302) == 0xBE);
99
+ REQUIRE(emu.readMemory(0x0303) == 0xEF);
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Round-trip: CPU state preserved
104
+ // ---------------------------------------------------------------------------
105
+
106
+ TEST_CASE("Emulator state round-trip preserves CPU registers", "[emulator][state]") {
107
+ Emulator emu;
108
+ emu.init();
109
+
110
+ // Set specific CPU register values
111
+ emu.setA(0x42);
112
+ emu.setX(0x13);
113
+ emu.setY(0x77);
114
+
115
+ // Export
116
+ size_t size = 0;
117
+ const uint8_t* data = emu.exportState(&size);
118
+ std::vector<uint8_t> stateCopy(data, data + size);
119
+
120
+ // Reset changes registers
121
+ emu.reset();
122
+ REQUIRE(emu.getA() != 0x42);
123
+
124
+ // Import
125
+ bool result = emu.importState(stateCopy.data(), stateCopy.size());
126
+ REQUIRE(result);
127
+
128
+ REQUIRE(emu.getA() == 0x42);
129
+ REQUIRE(emu.getX() == 0x13);
130
+ REQUIRE(emu.getY() == 0x77);
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Invalid data
135
+ // ---------------------------------------------------------------------------
136
+
137
+ TEST_CASE("Emulator importState rejects garbage data", "[emulator][state]") {
138
+ Emulator emu;
139
+ emu.init();
140
+
141
+ // Create garbage data that does not have a valid magic header
142
+ std::vector<uint8_t> garbage(1024, 0xFF);
143
+ bool result = emu.importState(garbage.data(), garbage.size());
144
+ REQUIRE_FALSE(result);
145
+ }
146
+
147
+ TEST_CASE("Emulator importState rejects too-small data", "[emulator][state]") {
148
+ Emulator emu;
149
+ emu.init();
150
+
151
+ // Data smaller than the minimum header (8 bytes for magic + version)
152
+ std::vector<uint8_t> tooSmall(4, 0x00);
153
+ bool result = emu.importState(tooSmall.data(), tooSmall.size());
154
+ REQUIRE_FALSE(result);
155
+ }
156
+
157
+ TEST_CASE("Emulator importState rejects empty data", "[emulator][state]") {
158
+ Emulator emu;
159
+ emu.init();
160
+
161
+ bool result = emu.importState(nullptr, 0);
162
+ REQUIRE_FALSE(result);
163
+ }