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,259 @@
1
+ /*
2
+ * test_block_device.cpp - Unit tests for SmartPort BlockDevice
3
+ */
4
+
5
+ #define CATCH_CONFIG_MAIN
6
+ #include "catch.hpp"
7
+
8
+ #include "block_device.hpp"
9
+
10
+ #include <cstring>
11
+ #include <vector>
12
+
13
+ using namespace a2e;
14
+
15
+ // ============================================================================
16
+ // Default construction
17
+ // ============================================================================
18
+
19
+ TEST_CASE("BlockDevice default constructed is not loaded", "[blockdev][ctor]") {
20
+ BlockDevice dev;
21
+ CHECK(dev.isLoaded() == false);
22
+ CHECK(dev.getTotalBlocks() == 0);
23
+ CHECK(dev.isModified() == false);
24
+ CHECK(dev.isWriteProtected() == false);
25
+ CHECK(dev.getFilename().empty());
26
+ }
27
+
28
+ // ============================================================================
29
+ // load with valid HDV data
30
+ // ============================================================================
31
+
32
+ TEST_CASE("BlockDevice load with raw block data", "[blockdev][load]") {
33
+ BlockDevice dev;
34
+
35
+ // Create a minimal HDV image: 4 blocks = 2048 bytes
36
+ const size_t numBlocks = 4;
37
+ const size_t dataSize = numBlocks * BlockDevice::BLOCK_SIZE;
38
+ std::vector<uint8_t> data(dataSize, 0x00);
39
+
40
+ // Fill first block with a pattern
41
+ for (size_t i = 0; i < BlockDevice::BLOCK_SIZE; i++) {
42
+ data[i] = static_cast<uint8_t>(i & 0xFF);
43
+ }
44
+
45
+ bool loaded = dev.load(data.data(), data.size(), "test.hdv");
46
+ REQUIRE(loaded == true);
47
+
48
+ CHECK(dev.isLoaded() == true);
49
+ CHECK(dev.getTotalBlocks() == numBlocks);
50
+ CHECK(dev.getFilename() == "test.hdv");
51
+ CHECK(dev.isModified() == false);
52
+ }
53
+
54
+ TEST_CASE("BlockDevice load with zero-size data fails", "[blockdev][load]") {
55
+ BlockDevice dev;
56
+ bool loaded = dev.load(nullptr, 0, "empty.hdv");
57
+ CHECK(loaded == false);
58
+ CHECK(dev.isLoaded() == false);
59
+ }
60
+
61
+ TEST_CASE("BlockDevice load with non-block-aligned size", "[blockdev][load]") {
62
+ BlockDevice dev;
63
+ // 1000 bytes is not a multiple of 512
64
+ std::vector<uint8_t> data(1000, 0x00);
65
+ bool loaded = dev.load(data.data(), data.size(), "weird.hdv");
66
+ // Implementation may reject or truncate - just verify no crash
67
+ // If it loads, totalBlocks should reflect the truncated count
68
+ if (loaded) {
69
+ CHECK(dev.getTotalBlocks() == 1); // 1000/512 = 1 full block
70
+ }
71
+ }
72
+
73
+ // ============================================================================
74
+ // readBlock
75
+ // ============================================================================
76
+
77
+ TEST_CASE("BlockDevice readBlock reads correct data", "[blockdev][readBlock]") {
78
+ BlockDevice dev;
79
+
80
+ const size_t numBlocks = 8;
81
+ std::vector<uint8_t> data(numBlocks * BlockDevice::BLOCK_SIZE, 0x00);
82
+
83
+ // Put distinctive pattern in block 3
84
+ for (size_t i = 0; i < BlockDevice::BLOCK_SIZE; i++) {
85
+ data[3 * BlockDevice::BLOCK_SIZE + i] = static_cast<uint8_t>(0xAA);
86
+ }
87
+
88
+ REQUIRE(dev.load(data.data(), data.size(), "test.hdv"));
89
+
90
+ uint8_t buffer[BlockDevice::BLOCK_SIZE];
91
+
92
+ // Read block 3
93
+ bool ok = dev.readBlock(3, buffer);
94
+ REQUIRE(ok);
95
+ for (size_t i = 0; i < BlockDevice::BLOCK_SIZE; i++) {
96
+ CHECK(buffer[i] == 0xAA);
97
+ }
98
+
99
+ // Read block 0 (should be all zeros)
100
+ ok = dev.readBlock(0, buffer);
101
+ REQUIRE(ok);
102
+ for (size_t i = 0; i < BlockDevice::BLOCK_SIZE; i++) {
103
+ CHECK(buffer[i] == 0x00);
104
+ }
105
+ }
106
+
107
+ TEST_CASE("BlockDevice readBlock out of range fails", "[blockdev][readBlock]") {
108
+ BlockDevice dev;
109
+
110
+ std::vector<uint8_t> data(4 * BlockDevice::BLOCK_SIZE, 0x00);
111
+ REQUIRE(dev.load(data.data(), data.size(), "test.hdv"));
112
+
113
+ uint8_t buffer[BlockDevice::BLOCK_SIZE];
114
+ bool ok = dev.readBlock(100, buffer); // Way out of range
115
+ CHECK(ok == false);
116
+ }
117
+
118
+ // ============================================================================
119
+ // writeBlock
120
+ // ============================================================================
121
+
122
+ TEST_CASE("BlockDevice writeBlock writes and reads back", "[blockdev][writeBlock]") {
123
+ BlockDevice dev;
124
+
125
+ std::vector<uint8_t> data(8 * BlockDevice::BLOCK_SIZE, 0x00);
126
+ REQUIRE(dev.load(data.data(), data.size(), "test.hdv"));
127
+
128
+ uint8_t writeData[BlockDevice::BLOCK_SIZE];
129
+ memset(writeData, 0x55, sizeof(writeData));
130
+
131
+ bool ok = dev.writeBlock(2, writeData);
132
+ REQUIRE(ok);
133
+
134
+ // Read it back
135
+ uint8_t readBuf[BlockDevice::BLOCK_SIZE];
136
+ ok = dev.readBlock(2, readBuf);
137
+ REQUIRE(ok);
138
+
139
+ CHECK(memcmp(writeData, readBuf, BlockDevice::BLOCK_SIZE) == 0);
140
+ }
141
+
142
+ TEST_CASE("BlockDevice writeBlock out of range fails", "[blockdev][writeBlock]") {
143
+ BlockDevice dev;
144
+
145
+ std::vector<uint8_t> data(4 * BlockDevice::BLOCK_SIZE, 0x00);
146
+ REQUIRE(dev.load(data.data(), data.size(), "test.hdv"));
147
+
148
+ uint8_t writeData[BlockDevice::BLOCK_SIZE];
149
+ memset(writeData, 0xFF, sizeof(writeData));
150
+
151
+ bool ok = dev.writeBlock(100, writeData);
152
+ CHECK(ok == false);
153
+ }
154
+
155
+ // ============================================================================
156
+ // isModified after writeBlock
157
+ // ============================================================================
158
+
159
+ TEST_CASE("BlockDevice isModified is true after writeBlock", "[blockdev][modified]") {
160
+ BlockDevice dev;
161
+
162
+ std::vector<uint8_t> data(4 * BlockDevice::BLOCK_SIZE, 0x00);
163
+ REQUIRE(dev.load(data.data(), data.size(), "test.hdv"));
164
+
165
+ CHECK(dev.isModified() == false);
166
+
167
+ uint8_t writeData[BlockDevice::BLOCK_SIZE];
168
+ memset(writeData, 0x42, sizeof(writeData));
169
+ dev.writeBlock(0, writeData);
170
+
171
+ CHECK(dev.isModified() == true);
172
+ }
173
+
174
+ // ============================================================================
175
+ // eject
176
+ // ============================================================================
177
+
178
+ TEST_CASE("BlockDevice eject clears state", "[blockdev][eject]") {
179
+ BlockDevice dev;
180
+
181
+ std::vector<uint8_t> data(8 * BlockDevice::BLOCK_SIZE, 0x00);
182
+ REQUIRE(dev.load(data.data(), data.size(), "test.hdv"));
183
+
184
+ CHECK(dev.isLoaded() == true);
185
+
186
+ dev.eject();
187
+
188
+ CHECK(dev.isLoaded() == false);
189
+ CHECK(dev.getTotalBlocks() == 0);
190
+ CHECK(dev.isModified() == false);
191
+ CHECK(dev.getFilename().empty());
192
+ }
193
+
194
+ // ============================================================================
195
+ // exportData
196
+ // ============================================================================
197
+
198
+ TEST_CASE("BlockDevice exportData returns valid pointer and size", "[blockdev][export]") {
199
+ BlockDevice dev;
200
+
201
+ const size_t numBlocks = 4;
202
+ std::vector<uint8_t> data(numBlocks * BlockDevice::BLOCK_SIZE, 0xAB);
203
+ REQUIRE(dev.load(data.data(), data.size(), "test.hdv"));
204
+
205
+ size_t exportSize = 0;
206
+ const uint8_t* exported = dev.exportData(&exportSize);
207
+
208
+ REQUIRE(exported != nullptr);
209
+ REQUIRE(exportSize > 0);
210
+ }
211
+
212
+ TEST_CASE("BlockDevice exportData on unloaded device returns null", "[blockdev][export]") {
213
+ BlockDevice dev;
214
+ size_t exportSize = 0;
215
+ const uint8_t* exported = dev.exportData(&exportSize);
216
+ CHECK(exported == nullptr);
217
+ CHECK(exportSize == 0);
218
+ }
219
+
220
+ // ============================================================================
221
+ // getFilename after load
222
+ // ============================================================================
223
+
224
+ TEST_CASE("BlockDevice getFilename returns loaded filename", "[blockdev][filename]") {
225
+ BlockDevice dev;
226
+
227
+ std::vector<uint8_t> data(2 * BlockDevice::BLOCK_SIZE, 0x00);
228
+
229
+ dev.load(data.data(), data.size(), "mydisk.po");
230
+ CHECK(dev.getFilename() == "mydisk.po");
231
+
232
+ dev.load(data.data(), data.size(), "another.2mg");
233
+ CHECK(dev.getFilename() == "another.2mg");
234
+ }
235
+
236
+ TEST_CASE("BlockDevice getFilename empty when not loaded", "[blockdev][filename]") {
237
+ BlockDevice dev;
238
+ CHECK(dev.getFilename().empty());
239
+ }
240
+
241
+ // ============================================================================
242
+ // Multiple load/eject cycles
243
+ // ============================================================================
244
+
245
+ TEST_CASE("BlockDevice can be reloaded after eject", "[blockdev][lifecycle]") {
246
+ BlockDevice dev;
247
+
248
+ std::vector<uint8_t> data1(4 * BlockDevice::BLOCK_SIZE, 0x11);
249
+ REQUIRE(dev.load(data1.data(), data1.size(), "first.hdv"));
250
+ CHECK(dev.getTotalBlocks() == 4);
251
+
252
+ dev.eject();
253
+ CHECK(dev.isLoaded() == false);
254
+
255
+ std::vector<uint8_t> data2(8 * BlockDevice::BLOCK_SIZE, 0x22);
256
+ REQUIRE(dev.load(data2.data(), data2.size(), "second.hdv"));
257
+ CHECK(dev.getTotalBlocks() == 8);
258
+ CHECK(dev.getFilename() == "second.hdv");
259
+ }
@@ -0,0 +1,219 @@
1
+ /*
2
+ * test_condition_evaluator.cpp - Unit tests for ConditionEvaluator
3
+ *
4
+ * This requires an Emulator instance since evaluate() and evaluateNumeric()
5
+ * take a const Emulator& parameter.
6
+ */
7
+
8
+ #define CATCH_CONFIG_MAIN
9
+ #include "catch.hpp"
10
+
11
+ #include "condition_evaluator.hpp"
12
+ #include "emulator.hpp"
13
+
14
+ using namespace a2e;
15
+
16
+ // Helper to create and initialize an emulator for testing.
17
+ // The emulator init() loads ROM and resets the CPU.
18
+ static Emulator& getEmulator() {
19
+ static Emulator emu;
20
+ static bool initialized = false;
21
+ if (!initialized) {
22
+ emu.init();
23
+ initialized = true;
24
+ }
25
+ return emu;
26
+ }
27
+
28
+ // ============================================================================
29
+ // Simple numeric expression
30
+ // ============================================================================
31
+
32
+ TEST_CASE("ConditionEvaluator numeric literal 42 evaluates to 42", "[condeval][numeric]") {
33
+ auto& emu = getEmulator();
34
+ int32_t result = ConditionEvaluator::evaluateNumeric("42", emu);
35
+ CHECK(result == 42);
36
+ }
37
+
38
+ TEST_CASE("ConditionEvaluator numeric literal 0 evaluates to 0", "[condeval][numeric]") {
39
+ auto& emu = getEmulator();
40
+ int32_t result = ConditionEvaluator::evaluateNumeric("0", emu);
41
+ CHECK(result == 0);
42
+ }
43
+
44
+ TEST_CASE("ConditionEvaluator numeric literal 255 evaluates to 255", "[condeval][numeric]") {
45
+ auto& emu = getEmulator();
46
+ int32_t result = ConditionEvaluator::evaluateNumeric("255", emu);
47
+ CHECK(result == 255);
48
+ }
49
+
50
+ // ============================================================================
51
+ // Hex literal
52
+ // ============================================================================
53
+
54
+ TEST_CASE("ConditionEvaluator hex literal $FF evaluates to 255", "[condeval][hex]") {
55
+ auto& emu = getEmulator();
56
+ int32_t result = ConditionEvaluator::evaluateNumeric("$FF", emu);
57
+ CHECK(result == 255);
58
+ }
59
+
60
+ TEST_CASE("ConditionEvaluator hex literal $00 evaluates to 0", "[condeval][hex]") {
61
+ auto& emu = getEmulator();
62
+ int32_t result = ConditionEvaluator::evaluateNumeric("$00", emu);
63
+ CHECK(result == 0);
64
+ }
65
+
66
+ TEST_CASE("ConditionEvaluator hex literal $FFFF evaluates to 65535", "[condeval][hex]") {
67
+ auto& emu = getEmulator();
68
+ int32_t result = ConditionEvaluator::evaluateNumeric("$FFFF", emu);
69
+ CHECK(result == 65535);
70
+ }
71
+
72
+ // ============================================================================
73
+ // Register comparison
74
+ // ============================================================================
75
+
76
+ TEST_CASE("ConditionEvaluator A register comparison", "[condeval][register]") {
77
+ auto& emu = getEmulator();
78
+
79
+ // After init(), A register has some value (typically 0 after cold reset).
80
+ // Set A to a known value via the emulator's CPU setter.
81
+ emu.setA(0);
82
+
83
+ bool result = ConditionEvaluator::evaluate("A == 0", emu);
84
+ CHECK(result == true);
85
+
86
+ // Negative test
87
+ result = ConditionEvaluator::evaluate("A == 1", emu);
88
+ CHECK(result == false);
89
+ }
90
+
91
+ TEST_CASE("ConditionEvaluator X register comparison", "[condeval][register]") {
92
+ auto& emu = getEmulator();
93
+ emu.setX(0x42);
94
+
95
+ bool result = ConditionEvaluator::evaluate("X == $42", emu);
96
+ CHECK(result == true);
97
+ }
98
+
99
+ TEST_CASE("ConditionEvaluator Y register comparison", "[condeval][register]") {
100
+ auto& emu = getEmulator();
101
+ emu.setY(0x10);
102
+
103
+ bool result = ConditionEvaluator::evaluate("Y == $10", emu);
104
+ CHECK(result == true);
105
+ }
106
+
107
+ // ============================================================================
108
+ // Invalid expression returns error
109
+ // ============================================================================
110
+
111
+ TEST_CASE("ConditionEvaluator unknown identifier sets error", "[condeval][error]") {
112
+ auto& emu = getEmulator();
113
+
114
+ // An expression with an unknown identifier triggers an error
115
+ ConditionEvaluator::evaluate("FOOBAR == 1", emu);
116
+ const char* err = ConditionEvaluator::getLastError();
117
+ CHECK(strlen(err) > 0);
118
+ }
119
+
120
+ TEST_CASE("ConditionEvaluator valid expression clears error", "[condeval][error]") {
121
+ auto& emu = getEmulator();
122
+
123
+ // First trigger an error with an unknown identifier
124
+ ConditionEvaluator::evaluate("FOOBAR == 1", emu);
125
+ CHECK(strlen(ConditionEvaluator::getLastError()) > 0);
126
+
127
+ // Now evaluate a valid expression
128
+ ConditionEvaluator::evaluate("42 == 42", emu);
129
+ // The error should be cleared (empty string)
130
+ CHECK(strlen(ConditionEvaluator::getLastError()) == 0);
131
+ }
132
+
133
+ // ============================================================================
134
+ // Arithmetic expression
135
+ // ============================================================================
136
+
137
+ TEST_CASE("ConditionEvaluator arithmetic $10 + $20 = $30", "[condeval][arithmetic]") {
138
+ auto& emu = getEmulator();
139
+ int32_t result = ConditionEvaluator::evaluateNumeric("$10 + $20", emu);
140
+ CHECK(result == 0x30);
141
+ }
142
+
143
+ TEST_CASE("ConditionEvaluator arithmetic subtraction", "[condeval][arithmetic]") {
144
+ auto& emu = getEmulator();
145
+ int32_t result = ConditionEvaluator::evaluateNumeric("100 - 30", emu);
146
+ CHECK(result == 70);
147
+ }
148
+
149
+ TEST_CASE("ConditionEvaluator arithmetic multiplication", "[condeval][arithmetic]") {
150
+ auto& emu = getEmulator();
151
+ int32_t result = ConditionEvaluator::evaluateNumeric("6 * 7", emu);
152
+ CHECK(result == 42);
153
+ }
154
+
155
+ // ============================================================================
156
+ // Boolean operations
157
+ // ============================================================================
158
+
159
+ TEST_CASE("ConditionEvaluator equality true", "[condeval][comparison]") {
160
+ auto& emu = getEmulator();
161
+ bool result = ConditionEvaluator::evaluate("42 == 42", emu);
162
+ CHECK(result == true);
163
+ }
164
+
165
+ TEST_CASE("ConditionEvaluator inequality", "[condeval][comparison]") {
166
+ auto& emu = getEmulator();
167
+ bool result = ConditionEvaluator::evaluate("1 != 2", emu);
168
+ CHECK(result == true);
169
+ }
170
+
171
+ TEST_CASE("ConditionEvaluator less than", "[condeval][comparison]") {
172
+ auto& emu = getEmulator();
173
+ bool result = ConditionEvaluator::evaluate("1 < 2", emu);
174
+ CHECK(result == true);
175
+ }
176
+
177
+ TEST_CASE("ConditionEvaluator greater than", "[condeval][comparison]") {
178
+ auto& emu = getEmulator();
179
+ bool result = ConditionEvaluator::evaluate("10 > 5", emu);
180
+ CHECK(result == true);
181
+ }
182
+
183
+ // ============================================================================
184
+ // Parenthesized expressions
185
+ // ============================================================================
186
+
187
+ TEST_CASE("ConditionEvaluator parenthesized expression", "[condeval][paren]") {
188
+ auto& emu = getEmulator();
189
+
190
+ // Parenthesized expressions are evaluated through parseOr which returns
191
+ // a boolean (truthy) value. So (2 + 3) becomes true (1), and 1 * 4 = 4.
192
+ // This is by design: the evaluator is a condition evaluator, not a
193
+ // general-purpose calculator. Parentheses are for grouping comparisons.
194
+ int32_t result = ConditionEvaluator::evaluateNumeric("(2 + 3) * 4", emu);
195
+ CHECK(result == 4);
196
+
197
+ // Verify parenthesized comparisons work as expected
198
+ bool cmpResult = ConditionEvaluator::evaluate("(A == A) && (1 < 2)", emu);
199
+ CHECK(cmpResult == true);
200
+ }
201
+
202
+ // ============================================================================
203
+ // Register numeric read
204
+ // ============================================================================
205
+
206
+ TEST_CASE("ConditionEvaluator reads register A as numeric", "[condeval][register_num]") {
207
+ auto& emu = getEmulator();
208
+ emu.setA(0xAB);
209
+ int32_t result = ConditionEvaluator::evaluateNumeric("A", emu);
210
+ CHECK(result == 0xAB);
211
+ }
212
+
213
+ TEST_CASE("ConditionEvaluator reads SP register", "[condeval][register_num]") {
214
+ auto& emu = getEmulator();
215
+ // SP should be a valid 8-bit value
216
+ int32_t result = ConditionEvaluator::evaluateNumeric("SP", emu);
217
+ CHECK(result >= 0);
218
+ CHECK(result <= 0xFF);
219
+ }