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,386 @@
1
+ /*
2
+ * test_mouse_card.cpp - Unit tests for MouseCard
3
+ *
4
+ * Tests the Apple Mouse Interface card implementation including:
5
+ * - Construction
6
+ * - Card metadata (name, preferred slot)
7
+ * - ROM presence
8
+ * - ROM data reading
9
+ * - Mouse delta input and position tracking
10
+ * - Button state tracking
11
+ * - Position clamping
12
+ * - PIA register access
13
+ * - Reset behavior
14
+ * - Serialization round-trip
15
+ * - Slot number configuration
16
+ */
17
+
18
+ #define CATCH_CONFIG_MAIN
19
+ #include "catch.hpp"
20
+
21
+ #include "mouse_card.hpp"
22
+ #include "roms.cpp"
23
+
24
+ #include <cstring>
25
+ #include <vector>
26
+
27
+ using namespace a2e;
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Construction
31
+ // ---------------------------------------------------------------------------
32
+
33
+ TEST_CASE("MouseCard constructor creates valid instance", "[mouse]") {
34
+ MouseCard card;
35
+ REQUIRE(card.getName() != nullptr);
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Card metadata
40
+ // ---------------------------------------------------------------------------
41
+
42
+ TEST_CASE("MouseCard getName returns Mouse", "[mouse]") {
43
+ MouseCard card;
44
+ REQUIRE(std::string(card.getName()) == "Mouse");
45
+ }
46
+
47
+ TEST_CASE("MouseCard getPreferredSlot returns 4", "[mouse]") {
48
+ MouseCard card;
49
+ REQUIRE(card.getPreferredSlot() == 4);
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // ROM presence
54
+ // ---------------------------------------------------------------------------
55
+
56
+ TEST_CASE("MouseCard hasROM returns true", "[mouse]") {
57
+ MouseCard card;
58
+ REQUIRE(card.hasROM());
59
+ }
60
+
61
+ TEST_CASE("MouseCard hasExpansionROM returns false", "[mouse]") {
62
+ MouseCard card;
63
+ REQUIRE_FALSE(card.hasExpansionROM());
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // ROM data reading
68
+ // ---------------------------------------------------------------------------
69
+
70
+ TEST_CASE("MouseCard readROM returns ROM data from embedded mouse ROM", "[mouse]") {
71
+ MouseCard card;
72
+
73
+ // The mouse ROM should not be all zeros
74
+ bool allZero = true;
75
+ for (int i = 0; i < 256; ++i) {
76
+ if (card.readROM(static_cast<uint8_t>(i)) != 0x00) {
77
+ allZero = false;
78
+ break;
79
+ }
80
+ }
81
+ REQUIRE_FALSE(allZero);
82
+ }
83
+
84
+ TEST_CASE("MouseCard readROM returns ROM data not all 0xFF", "[mouse]") {
85
+ MouseCard card;
86
+
87
+ bool allFF = true;
88
+ for (int i = 0; i < 256; ++i) {
89
+ if (card.readROM(static_cast<uint8_t>(i)) != 0xFF) {
90
+ allFF = false;
91
+ break;
92
+ }
93
+ }
94
+ REQUIRE_FALSE(allFF);
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Mouse delta input: addDelta
99
+ // ---------------------------------------------------------------------------
100
+
101
+ TEST_CASE("MouseCard initial position is 0,0", "[mouse]") {
102
+ MouseCard card;
103
+ REQUIRE(card.getMouseX() == 0);
104
+ REQUIRE(card.getMouseY() == 0);
105
+ }
106
+
107
+ TEST_CASE("MouseCard addDelta changes position", "[mouse]") {
108
+ MouseCard card;
109
+
110
+ card.addDelta(10, 20);
111
+ REQUIRE(card.getMouseX() == 10);
112
+ REQUIRE(card.getMouseY() == 20);
113
+ }
114
+
115
+ TEST_CASE("MouseCard addDelta sets moved flag", "[mouse]") {
116
+ MouseCard card;
117
+
118
+ REQUIRE_FALSE(card.getMoved());
119
+ card.addDelta(5, 5);
120
+ REQUIRE(card.getMoved());
121
+ }
122
+
123
+ TEST_CASE("MouseCard addDelta accumulates", "[mouse]") {
124
+ MouseCard card;
125
+
126
+ card.addDelta(10, 10);
127
+ card.addDelta(5, -3);
128
+ REQUIRE(card.getMouseX() == 15);
129
+ REQUIRE(card.getMouseY() == 7);
130
+ }
131
+
132
+ TEST_CASE("MouseCard addDelta with zero does not change position", "[mouse]") {
133
+ MouseCard card;
134
+
135
+ card.addDelta(0, 0);
136
+ REQUIRE(card.getMouseX() == 0);
137
+ REQUIRE(card.getMouseY() == 0);
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Button state
142
+ // ---------------------------------------------------------------------------
143
+
144
+ TEST_CASE("MouseCard button starts unpressed", "[mouse]") {
145
+ MouseCard card;
146
+ REQUIRE_FALSE(card.getMouseButton());
147
+ }
148
+
149
+ TEST_CASE("MouseCard setMouseButton true sets button pressed", "[mouse]") {
150
+ MouseCard card;
151
+
152
+ card.setMouseButton(true);
153
+ REQUIRE(card.getMouseButton());
154
+ }
155
+
156
+ TEST_CASE("MouseCard setMouseButton sets buttonChanged flag", "[mouse]") {
157
+ MouseCard card;
158
+
159
+ REQUIRE_FALSE(card.getButtonChanged());
160
+ card.setMouseButton(true);
161
+ REQUIRE(card.getButtonChanged());
162
+ }
163
+
164
+ TEST_CASE("MouseCard setMouseButton false after true clears button", "[mouse]") {
165
+ MouseCard card;
166
+
167
+ card.setMouseButton(true);
168
+ REQUIRE(card.getMouseButton());
169
+
170
+ card.setMouseButton(false);
171
+ REQUIRE_FALSE(card.getMouseButton());
172
+ }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // Position clamping
176
+ // ---------------------------------------------------------------------------
177
+
178
+ TEST_CASE("MouseCard default clamp range is 0-1023", "[mouse]") {
179
+ MouseCard card;
180
+ REQUIRE(card.getClampMinX() == 0);
181
+ REQUIRE(card.getClampMaxX() == 1023);
182
+ REQUIRE(card.getClampMinY() == 0);
183
+ REQUIRE(card.getClampMaxY() == 1023);
184
+ }
185
+
186
+ TEST_CASE("MouseCard position clamps to max bounds", "[mouse]") {
187
+ MouseCard card;
188
+
189
+ // Move far beyond the default max clamp of 1023
190
+ card.addDelta(2000, 2000);
191
+
192
+ REQUIRE(card.getMouseX() <= card.getClampMaxX());
193
+ REQUIRE(card.getMouseY() <= card.getClampMaxY());
194
+ }
195
+
196
+ TEST_CASE("MouseCard position clamps to min bounds", "[mouse]") {
197
+ MouseCard card;
198
+
199
+ // Move far negative beyond the default min clamp of 0
200
+ card.addDelta(-1000, -1000);
201
+
202
+ REQUIRE(card.getMouseX() >= card.getClampMinX());
203
+ REQUIRE(card.getMouseY() >= card.getClampMinY());
204
+ }
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // PIA register access
208
+ // ---------------------------------------------------------------------------
209
+
210
+ TEST_CASE("MouseCard readIO for PIA registers does not crash", "[mouse]") {
211
+ MouseCard card;
212
+
213
+ // PIA has 4 registers at offsets 0-3
214
+ for (uint8_t offset = 0; offset < 4; ++offset) {
215
+ uint8_t val = card.readIO(offset);
216
+ (void)val;
217
+ }
218
+ REQUIRE(true);
219
+ }
220
+
221
+ TEST_CASE("MouseCard writeIO for PIA registers does not crash", "[mouse]") {
222
+ MouseCard card;
223
+
224
+ for (uint8_t offset = 0; offset < 4; ++offset) {
225
+ card.writeIO(offset, 0x00);
226
+ card.writeIO(offset, 0xFF);
227
+ }
228
+ REQUIRE(true);
229
+ }
230
+
231
+ TEST_CASE("MouseCard peekIO does not crash", "[mouse]") {
232
+ MouseCard card;
233
+
234
+ for (uint8_t offset = 0; offset < 4; ++offset) {
235
+ uint8_t val = card.peekIO(offset);
236
+ (void)val;
237
+ }
238
+ REQUIRE(true);
239
+ }
240
+
241
+ TEST_CASE("MouseCard PIA debug accessors return initial values", "[mouse]") {
242
+ MouseCard card;
243
+
244
+ // After construction, PIA registers should be 0
245
+ REQUIRE(card.getDDRA() == 0);
246
+ REQUIRE(card.getDDRB() == 0);
247
+ REQUIRE(card.getORA() == 0);
248
+ REQUIRE(card.getORB() == 0);
249
+ REQUIRE(card.getCRA() == 0);
250
+ REQUIRE(card.getCRB() == 0);
251
+ }
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // Reset
255
+ // ---------------------------------------------------------------------------
256
+
257
+ TEST_CASE("MouseCard reset clears state", "[mouse]") {
258
+ MouseCard card;
259
+
260
+ // Set up some state
261
+ card.addDelta(100, 200);
262
+ card.setMouseButton(true);
263
+
264
+ card.reset();
265
+
266
+ // After reset, position and button should be cleared
267
+ REQUIRE(card.getMouseX() == 0);
268
+ REQUIRE(card.getMouseY() == 0);
269
+ REQUIRE_FALSE(card.getMouseButton());
270
+ REQUIRE_FALSE(card.getMoved());
271
+ REQUIRE_FALSE(card.getButtonChanged());
272
+ }
273
+
274
+ TEST_CASE("MouseCard reset clears PIA registers", "[mouse]") {
275
+ MouseCard card;
276
+
277
+ // Write to PIA registers to set state
278
+ card.writeIO(0, 0xFF);
279
+ card.writeIO(1, 0xFF);
280
+
281
+ card.reset();
282
+
283
+ REQUIRE(card.getDDRA() == 0);
284
+ REQUIRE(card.getDDRB() == 0);
285
+ REQUIRE(card.getORA() == 0);
286
+ REQUIRE(card.getORB() == 0);
287
+ }
288
+
289
+ // ---------------------------------------------------------------------------
290
+ // Serialization round-trip
291
+ // ---------------------------------------------------------------------------
292
+
293
+ TEST_CASE("MouseCard getStateSize is correct", "[mouse]") {
294
+ MouseCard card;
295
+ REQUIRE(card.getStateSize() == MouseCard::STATE_SIZE);
296
+ }
297
+
298
+ TEST_CASE("MouseCard serialize/deserialize round-trip", "[mouse]") {
299
+ MouseCard card1;
300
+
301
+ // Set up some state
302
+ card1.addDelta(50, 75);
303
+ card1.setMouseButton(true);
304
+
305
+ int16_t xBefore = card1.getMouseX();
306
+ int16_t yBefore = card1.getMouseY();
307
+ bool btnBefore = card1.getMouseButton();
308
+
309
+ // Serialize
310
+ std::vector<uint8_t> buffer(card1.getStateSize());
311
+ size_t written = card1.serialize(buffer.data(), buffer.size());
312
+ REQUIRE(written > 0);
313
+ REQUIRE(written <= buffer.size());
314
+
315
+ // Deserialize
316
+ MouseCard card2;
317
+ size_t consumed = card2.deserialize(buffer.data(), written);
318
+ REQUIRE(consumed > 0);
319
+
320
+ // Verify state preserved
321
+ REQUIRE(card2.getMouseX() == xBefore);
322
+ REQUIRE(card2.getMouseY() == yBefore);
323
+ REQUIRE(card2.getMouseButton() == btnBefore);
324
+ }
325
+
326
+ // ---------------------------------------------------------------------------
327
+ // Slot number
328
+ // ---------------------------------------------------------------------------
329
+
330
+ TEST_CASE("MouseCard default slot is 4", "[mouse]") {
331
+ MouseCard card;
332
+ REQUIRE(card.getSlotNumber() == 4);
333
+ }
334
+
335
+ TEST_CASE("MouseCard setSlotNumber changes slot", "[mouse]") {
336
+ MouseCard card;
337
+
338
+ card.setSlotNumber(2);
339
+ REQUIRE(card.getSlotNumber() == 2);
340
+
341
+ card.setSlotNumber(7);
342
+ REQUIRE(card.getSlotNumber() == 7);
343
+ }
344
+
345
+ // ---------------------------------------------------------------------------
346
+ // IRQ state
347
+ // ---------------------------------------------------------------------------
348
+
349
+ TEST_CASE("MouseCard isIRQActive is false initially", "[mouse]") {
350
+ MouseCard card;
351
+ REQUIRE_FALSE(card.isIRQActive());
352
+ }
353
+
354
+ // ---------------------------------------------------------------------------
355
+ // Update
356
+ // ---------------------------------------------------------------------------
357
+
358
+ TEST_CASE("MouseCard update does not crash", "[mouse]") {
359
+ MouseCard card;
360
+ card.update(100);
361
+ card.update(1000);
362
+ REQUIRE(true);
363
+ }
364
+
365
+ // ---------------------------------------------------------------------------
366
+ // Mode
367
+ // ---------------------------------------------------------------------------
368
+
369
+ TEST_CASE("MouseCard initial mode is 0", "[mouse]") {
370
+ MouseCard card;
371
+ REQUIRE(card.getMode() == 0);
372
+ }
373
+
374
+ // ---------------------------------------------------------------------------
375
+ // Debug accessors
376
+ // ---------------------------------------------------------------------------
377
+
378
+ TEST_CASE("MouseCard getLastCommand returns 0 initially", "[mouse]") {
379
+ MouseCard card;
380
+ REQUIRE(card.getLastCommand() == 0);
381
+ }
382
+
383
+ TEST_CASE("MouseCard getResponseState returns 0 initially", "[mouse]") {
384
+ MouseCard card;
385
+ REQUIRE(card.getResponseState() == 0);
386
+ }
@@ -0,0 +1,248 @@
1
+ /*
2
+ * test_pascal.cpp - Unit tests for Apple Pascal filesystem parser
3
+ *
4
+ * Tests the Pascal filesystem reader including:
5
+ * - Format detection (isPascal)
6
+ * - Volume information parsing
7
+ * - Catalog reading
8
+ * - File data retrieval
9
+ * - File type mapping for viewer
10
+ */
11
+
12
+ #define CATCH_CONFIG_MAIN
13
+ #include "catch.hpp"
14
+
15
+ #include "pascal.hpp"
16
+ #include "disk_image_builder.hpp"
17
+
18
+ #include <array>
19
+ #include <cstring>
20
+ #include <vector>
21
+
22
+ using namespace a2e;
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // isPascal - Format detection
26
+ // ---------------------------------------------------------------------------
27
+
28
+ TEST_CASE("isPascal returns true for valid Pascal image", "[pascal][detection]") {
29
+ test::PascalDiskBuilder builder("PASCAL");
30
+ REQUIRE(Pascal::isPascal(builder.data(), builder.size()));
31
+ }
32
+
33
+ TEST_CASE("isPascal returns false for zeroed data", "[pascal][detection]") {
34
+ std::vector<uint8_t> zeroed(143360, 0x00);
35
+ REQUIRE_FALSE(Pascal::isPascal(zeroed.data(), zeroed.size()));
36
+ }
37
+
38
+ TEST_CASE("isPascal returns false for too-small data", "[pascal][detection]") {
39
+ std::vector<uint8_t> small(512, 0x00);
40
+ REQUIRE_FALSE(Pascal::isPascal(small.data(), small.size()));
41
+ }
42
+
43
+ TEST_CASE("isPascal returns false for null pointer", "[pascal][detection]") {
44
+ REQUIRE_FALSE(Pascal::isPascal(nullptr, 0));
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // parseVolumeInfo - Volume information
49
+ // ---------------------------------------------------------------------------
50
+
51
+ TEST_CASE("parseVolumeInfo returns correct volume name", "[pascal][volume]") {
52
+ test::PascalDiskBuilder builder("MYDISC");
53
+
54
+ PascalVolumeInfo info;
55
+ bool result = Pascal::parseVolumeInfo(builder.data(), builder.size(), &info);
56
+ REQUIRE(result);
57
+ CHECK(std::string(info.volumeName) == "MYDISC");
58
+ }
59
+
60
+ TEST_CASE("parseVolumeInfo returns correct total blocks", "[pascal][volume]") {
61
+ test::PascalDiskBuilder builder("TEST");
62
+
63
+ PascalVolumeInfo info;
64
+ bool result = Pascal::parseVolumeInfo(builder.data(), builder.size(), &info);
65
+ REQUIRE(result);
66
+ CHECK(info.totalBlocks == 280);
67
+ }
68
+
69
+ TEST_CASE("parseVolumeInfo file count reflects added files", "[pascal][volume]") {
70
+ test::PascalDiskBuilder builder("TEST");
71
+
72
+ const uint8_t data1[] = "FIRST";
73
+ const uint8_t data2[] = "SECOND";
74
+ builder.addFile("FILE1", 2, data1, sizeof(data1)); // 2 = Code file
75
+ builder.addFile("FILE2", 3, data2, sizeof(data2)); // 3 = Text file
76
+
77
+ PascalVolumeInfo info;
78
+ bool result = Pascal::parseVolumeInfo(builder.data(), builder.size(), &info);
79
+ REQUIRE(result);
80
+ CHECK(info.fileCount == 2);
81
+ }
82
+
83
+ TEST_CASE("parseVolumeInfo returns false for zeroed data", "[pascal][volume]") {
84
+ std::vector<uint8_t> zeroed(143360, 0x00);
85
+ PascalVolumeInfo info;
86
+ REQUIRE_FALSE(Pascal::parseVolumeInfo(zeroed.data(), zeroed.size(), &info));
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // readCatalog - Catalog reading
91
+ // ---------------------------------------------------------------------------
92
+
93
+ TEST_CASE("readCatalog returns correct file count", "[pascal][catalog]") {
94
+ test::PascalDiskBuilder builder("TEST");
95
+
96
+ const uint8_t data[] = "HELLO PASCAL";
97
+ builder.addFile("GREETING", 3, data, sizeof(data)); // 3 = Text
98
+
99
+ PascalCatalogEntry entries[32];
100
+ int count = Pascal::readCatalog(builder.data(), builder.size(), entries, 32);
101
+ REQUIRE(count == 1);
102
+ }
103
+
104
+ TEST_CASE("Catalog entry has correct filename", "[pascal][catalog]") {
105
+ test::PascalDiskBuilder builder("TEST");
106
+
107
+ const uint8_t data[] = "DATA";
108
+ builder.addFile("MYCODE", 2, data, sizeof(data)); // 2 = Code
109
+
110
+ PascalCatalogEntry entries[32];
111
+ int count = Pascal::readCatalog(builder.data(), builder.size(), entries, 32);
112
+ REQUIRE(count == 1);
113
+ CHECK(std::string(entries[0].filename) == "MYCODE");
114
+ }
115
+
116
+ TEST_CASE("Catalog entry has correct file type name", "[pascal][catalog]") {
117
+ test::PascalDiskBuilder builder("TEST");
118
+
119
+ const uint8_t data[] = "CONTENT";
120
+ builder.addFile("SOURCE", 3, data, sizeof(data)); // 3 = Text
121
+
122
+ PascalCatalogEntry entries[32];
123
+ int count = Pascal::readCatalog(builder.data(), builder.size(), entries, 32);
124
+ REQUIRE(count == 1);
125
+ CHECK(entries[0].fileType == 3);
126
+ CHECK(std::string(entries[0].fileTypeName) == "TEXT");
127
+ }
128
+
129
+ TEST_CASE("readCatalog with empty disk returns zero", "[pascal][catalog]") {
130
+ test::PascalDiskBuilder builder("EMPTY");
131
+
132
+ PascalCatalogEntry entries[32];
133
+ int count = Pascal::readCatalog(builder.data(), builder.size(), entries, 32);
134
+ REQUIRE(count == 0);
135
+ }
136
+
137
+ TEST_CASE("Multiple files in Pascal catalog", "[pascal][catalog]") {
138
+ test::PascalDiskBuilder builder("MULTI");
139
+
140
+ const uint8_t d1[] = "AAA";
141
+ const uint8_t d2[] = "BBB";
142
+ const uint8_t d3[] = "CCC";
143
+ builder.addFile("ALPHA", 3, d1, sizeof(d1)); // Text
144
+ builder.addFile("BETA", 2, d2, sizeof(d2)); // Code
145
+ builder.addFile("GAMMA", 5, d3, sizeof(d3)); // Data
146
+
147
+ PascalCatalogEntry entries[32];
148
+ int count = Pascal::readCatalog(builder.data(), builder.size(), entries, 32);
149
+ REQUIRE(count == 3);
150
+ }
151
+
152
+ TEST_CASE("Catalog entry has valid block range", "[pascal][catalog]") {
153
+ test::PascalDiskBuilder builder("TEST");
154
+
155
+ const uint8_t data[] = "BLOCK DATA";
156
+ builder.addFile("BLKFILE", 2, data, sizeof(data)); // Code
157
+
158
+ PascalCatalogEntry entries[32];
159
+ int count = Pascal::readCatalog(builder.data(), builder.size(), entries, 32);
160
+ REQUIRE(count == 1);
161
+ CHECK(entries[0].startBlock >= 6); // First data block is after directory
162
+ CHECK(entries[0].nextBlock > entries[0].startBlock);
163
+ }
164
+
165
+ TEST_CASE("Catalog entry fileSize is computed correctly", "[pascal][catalog]") {
166
+ test::PascalDiskBuilder builder("TEST");
167
+
168
+ // Create a file smaller than one block
169
+ std::vector<uint8_t> data(100, 0x42);
170
+ builder.addFile("SMALL", 5, data.data(), static_cast<int>(data.size()));
171
+
172
+ PascalCatalogEntry entries[32];
173
+ int count = Pascal::readCatalog(builder.data(), builder.size(), entries, 32);
174
+ REQUIRE(count == 1);
175
+ CHECK(entries[0].fileSize == 100);
176
+ }
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // readFile - File data retrieval
180
+ // ---------------------------------------------------------------------------
181
+
182
+ TEST_CASE("readFile retrieves correct data", "[pascal][readFile]") {
183
+ test::PascalDiskBuilder builder("TEST");
184
+
185
+ std::vector<uint8_t> fileData(64);
186
+ for (int i = 0; i < 64; i++) {
187
+ fileData[i] = static_cast<uint8_t>(i);
188
+ }
189
+ builder.addFile("SEQDATA", 5, fileData.data(), static_cast<int>(fileData.size()));
190
+
191
+ PascalCatalogEntry entries[32];
192
+ int count = Pascal::readCatalog(builder.data(), builder.size(), entries, 32);
193
+ REQUIRE(count == 1);
194
+
195
+ std::vector<uint8_t> outBuf(1024, 0);
196
+ int bytesRead = Pascal::readFile(builder.data(), builder.size(),
197
+ &entries[0], outBuf.data(),
198
+ static_cast<int>(outBuf.size()));
199
+ REQUIRE(bytesRead > 0);
200
+
201
+ // Verify the data bytes match
202
+ for (int i = 0; i < 64; i++) {
203
+ INFO("Byte at offset " << i);
204
+ CHECK(outBuf[i] == static_cast<uint8_t>(i));
205
+ }
206
+ }
207
+
208
+ TEST_CASE("readFile with code file type", "[pascal][readFile]") {
209
+ test::PascalDiskBuilder builder("TEST");
210
+
211
+ std::vector<uint8_t> codeData = {0xEA, 0xEA, 0x60, 0x00}; // NOP NOP RTS BRK
212
+ builder.addFile("PROG", 2, codeData.data(), static_cast<int>(codeData.size())); // 2 = Code
213
+
214
+ PascalCatalogEntry entries[32];
215
+ int count = Pascal::readCatalog(builder.data(), builder.size(), entries, 32);
216
+ REQUIRE(count == 1);
217
+
218
+ std::vector<uint8_t> outBuf(1024, 0);
219
+ int bytesRead = Pascal::readFile(builder.data(), builder.size(),
220
+ &entries[0], outBuf.data(),
221
+ static_cast<int>(outBuf.size()));
222
+ REQUIRE(bytesRead >= 4);
223
+ CHECK(outBuf[0] == 0xEA);
224
+ CHECK(outBuf[1] == 0xEA);
225
+ CHECK(outBuf[2] == 0x60);
226
+ CHECK(outBuf[3] == 0x00);
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // mapFileTypeForViewer - File type mapping
231
+ // ---------------------------------------------------------------------------
232
+
233
+ TEST_CASE("mapFileTypeForViewer maps Text type", "[pascal][fileType]") {
234
+ // Pascal file type 3 = TEXT -> text viewer (0x00)
235
+ int viewerType = Pascal::mapFileTypeForViewer(3);
236
+ CHECK(viewerType == 0x00); // Text viewer
237
+ }
238
+
239
+ TEST_CASE("mapFileTypeForViewer maps Code type to hex dump", "[pascal][fileType]") {
240
+ // Pascal file type 2 = CODE -> not mapped (-1, hex dump)
241
+ int viewerType = Pascal::mapFileTypeForViewer(2);
242
+ CHECK(viewerType == -1);
243
+ }
244
+
245
+ TEST_CASE("mapFileTypeForViewer returns -1 for unknown type", "[pascal][fileType]") {
246
+ int viewerType = Pascal::mapFileTypeForViewer(99);
247
+ CHECK(viewerType == -1);
248
+ }