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,654 @@
1
+ /*
2
+ * mouse_card.cpp - Apple Mouse Interface card implementation
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #include "mouse_card.hpp"
9
+ #include "roms.cpp" // For embedded ROM data
10
+ #include "../types.hpp"
11
+ #include <algorithm>
12
+ #include <cstring>
13
+
14
+ namespace a2e {
15
+
16
+ // Mode byte bits
17
+ static constexpr uint8_t MODE_MOUSE_ON = (1 << 0);
18
+ static constexpr uint8_t MODE_INT_MOVEMENT = (1 << 1);
19
+ static constexpr uint8_t MODE_INT_BUTTON = (1 << 2);
20
+ static constexpr uint8_t MODE_INT_VBL = (1 << 3);
21
+
22
+ // Status byte bits
23
+ static constexpr uint8_t STAT_PREV_BUTTON1 = (1 << 0);
24
+ static constexpr uint8_t STAT_INT_MOVEMENT = (1 << 1);
25
+ static constexpr uint8_t STAT_INT_BUTTON = (1 << 2);
26
+ static constexpr uint8_t STAT_INT_VBL = (1 << 3);
27
+ static constexpr uint8_t STAT_CURR_BUTTON1 = (1 << 4);
28
+ static constexpr uint8_t STAT_MOVEMENT_SINCE_READMOUSE = (1 << 5);
29
+ static constexpr uint8_t STAT_PREV_BUTTON0 = (1 << 6);
30
+ static constexpr uint8_t STAT_CURR_BUTTON0 = (1 << 7);
31
+
32
+ // PIA register select (lower 2 bits of offset)
33
+ static constexpr uint8_t PIA_PORT_A = 0; // Port A data / DDRA
34
+ static constexpr uint8_t PIA_CRA = 1; // Control Register A
35
+ static constexpr uint8_t PIA_PORT_B = 2; // Port B data / DDRB
36
+ static constexpr uint8_t PIA_CRB = 3; // Control Register B
37
+
38
+ // Command codes (high nibble of command byte)
39
+ static constexpr uint8_t CMD_SET = 0x00;
40
+ static constexpr uint8_t CMD_READ = 0x10;
41
+ static constexpr uint8_t CMD_SERV = 0x20;
42
+ static constexpr uint8_t CMD_CLEAR = 0x30;
43
+ static constexpr uint8_t CMD_POS = 0x40;
44
+ static constexpr uint8_t CMD_INIT = 0x50;
45
+ static constexpr uint8_t CMD_CLAMP = 0x60;
46
+ static constexpr uint8_t CMD_HOME = 0x70;
47
+
48
+ MouseCard::MouseCard()
49
+ : rom_(roms::ROM_MOUSE)
50
+ , romSize_(roms::ROM_MOUSE_SIZE)
51
+ {
52
+ reset();
53
+ }
54
+
55
+ void MouseCard::reset() {
56
+ // Reset PIA
57
+ ddra_ = 0;
58
+ ddrb_ = 0;
59
+ ora_ = 0;
60
+ orb_ = 0;
61
+ ira_ = 0;
62
+ irb_ = 0;
63
+ cra_ = 0;
64
+ crb_ = 0;
65
+
66
+ // Reset mouse state
67
+ mouseX_ = 0;
68
+ mouseY_ = 0;
69
+ mouseButton_ = false;
70
+ lastButton_ = false;
71
+ moved_ = false;
72
+ buttonChanged_ = false;
73
+ mode_ = 0;
74
+
75
+ // Default clamp bounds
76
+ clampMinX_ = 0;
77
+ clampMaxX_ = 1023;
78
+ clampMinY_ = 0;
79
+ clampMaxY_ = 1023;
80
+
81
+ // Reset interrupt state
82
+ irqActive_ = false;
83
+ vblInterruptPending_ = false;
84
+ moveInterruptPending_ = false;
85
+ buttonInterruptPending_ = false;
86
+
87
+ // Reset VBL tracking
88
+ wasInVBL_ = false;
89
+
90
+ // Reset protocol state machine
91
+ byState_ = 0;
92
+ std::memset(byBuff_, 0, sizeof(byBuff_));
93
+ nBuffPos_ = 0;
94
+ nDataLen_ = 1;
95
+ by6821B_ = 0x40; // BIT6 set (MCU ready signal)
96
+ irb_ = by6821B_; // Reflect in Port B input
97
+ lastCommand_ = 0;
98
+
99
+ // Reset snapshot
100
+ snapX_ = 0;
101
+ snapY_ = 0;
102
+ }
103
+
104
+ // ============================================================================
105
+ // PIA Register Access
106
+ // ============================================================================
107
+
108
+ uint8_t MouseCard::readIO(uint8_t offset) {
109
+ uint8_t reg = offset & 0x03;
110
+
111
+ switch (reg) {
112
+ case PIA_PORT_A: {
113
+ if (cra_ & 0x04) {
114
+ // Data register selected: return (output & DDR) | (input & ~DDR)
115
+ uint8_t value = (ora_ & ddra_) | (ira_ & ~ddra_);
116
+ // Reading data port clears IRQ flags (bits 7,6) in CRA
117
+ cra_ &= 0x3F;
118
+ updateIRQState();
119
+ return value;
120
+ } else {
121
+ // DDR selected
122
+ return ddra_;
123
+ }
124
+ }
125
+
126
+ case PIA_CRA:
127
+ return cra_;
128
+
129
+ case PIA_PORT_B: {
130
+ if (crb_ & 0x04) {
131
+ // Data register selected
132
+ uint8_t value = (orb_ & ddrb_) | (irb_ & ~ddrb_);
133
+ // Reading data port clears IRQ flags (bits 7,6) in CRB
134
+ crb_ &= 0x3F;
135
+ updateIRQState();
136
+ return value;
137
+ } else {
138
+ return ddrb_;
139
+ }
140
+ }
141
+
142
+ case PIA_CRB:
143
+ return crb_;
144
+ }
145
+
146
+ return 0xFF;
147
+ }
148
+
149
+ void MouseCard::writeIO(uint8_t offset, uint8_t value) {
150
+ uint8_t reg = offset & 0x03;
151
+
152
+ switch (reg) {
153
+ case PIA_PORT_A: {
154
+ if (cra_ & 0x04) {
155
+ // Write to output register A
156
+ ora_ = value;
157
+ } else {
158
+ // Write to DDR A
159
+ ddra_ = value;
160
+ }
161
+ break;
162
+ }
163
+
164
+ case PIA_CRA:
165
+ // Bits 6,7 are read-only IRQ flags
166
+ cra_ = (cra_ & 0xC0) | (value & 0x3F);
167
+ updateIRQState();
168
+ break;
169
+
170
+ case PIA_PORT_B: {
171
+ if (crb_ & 0x04) {
172
+ // Write to output register B
173
+ orb_ = value;
174
+ on6821_B(value); // Detect clock transitions
175
+ } else {
176
+ // Write to DDR B
177
+ ddrb_ = value;
178
+ }
179
+ break;
180
+ }
181
+
182
+ case PIA_CRB:
183
+ crb_ = (crb_ & 0xC0) | (value & 0x3F);
184
+ updateIRQState();
185
+ break;
186
+ }
187
+ }
188
+
189
+ uint8_t MouseCard::peekIO(uint8_t offset) const {
190
+ uint8_t reg = offset & 0x03;
191
+ switch (reg) {
192
+ case PIA_PORT_A:
193
+ if (cra_ & 0x04) {
194
+ return (ora_ & ddra_) | (ira_ & ~ddra_);
195
+ }
196
+ return ddra_;
197
+ case PIA_CRA: return cra_;
198
+ case PIA_PORT_B:
199
+ if (crb_ & 0x04) {
200
+ return (orb_ & ddrb_) | (irb_ & ~ddrb_);
201
+ }
202
+ return ddrb_;
203
+ case PIA_CRB: return crb_;
204
+ }
205
+ return 0xFF;
206
+ }
207
+
208
+ // ============================================================================
209
+ // ROM Access (banked, firmware runs natively)
210
+ // ============================================================================
211
+
212
+ uint8_t MouseCard::readROM(uint8_t offset) {
213
+ // Port B bits 1-3 select which 256-byte page of the 2KB ROM
214
+ uint16_t romBank = static_cast<uint16_t>(by6821B_ & 0x0E) << 7;
215
+ uint16_t romOffset = romBank | offset;
216
+ if (rom_ && romOffset < romSize_) {
217
+ return rom_[romOffset];
218
+ }
219
+ return 0xFF;
220
+ }
221
+
222
+ // ============================================================================
223
+ // PIA Command Protocol (AppleWin-style)
224
+ // ============================================================================
225
+
226
+ void MouseCard::on6821_B(uint8_t byData) {
227
+ // Detect transitions on Port B bits 1-5 to determine when the firmware
228
+ // is writing a command/data byte or reading a response byte.
229
+ // Matches AppleWin's CMouseInterface::On6821_B() exactly.
230
+
231
+ uint8_t byDiff = (by6821B_ ^ byData) & 0x3E; // Only check bits 1-5
232
+ if (!byDiff) return;
233
+
234
+ // Update bits 1-5 from firmware output; bits 0,6,7 are MCU-managed
235
+ by6821B_ &= ~0x3E;
236
+ by6821B_ |= byData & 0x3E;
237
+
238
+ // BIT5: Write strobe (firmware writing data to MCU)
239
+ if (byDiff & 0x20) {
240
+ if (byData & 0x20) {
241
+ // Rising edge: MCU signals "ready to read"
242
+ by6821B_ |= 0x80; // Set BIT7
243
+ } else {
244
+ // Falling edge: clock data in from Port A
245
+ byBuff_[nBuffPos_++] = ora_;
246
+ if (nBuffPos_ == 1) {
247
+ onCommand();
248
+ }
249
+ if (nBuffPos_ == nDataLen_ || nBuffPos_ > 7) {
250
+ onWrite();
251
+ nBuffPos_ = 0;
252
+ }
253
+ by6821B_ &= ~0x80; // Clear BIT7 for next reading
254
+ }
255
+ }
256
+
257
+ // BIT4: Read strobe (firmware reading data from MCU)
258
+ if (byDiff & 0x10) {
259
+ if (byData & 0x10) {
260
+ // Rising edge: MCU prepares next value
261
+ by6821B_ &= ~0x40; // Clear BIT6
262
+ } else {
263
+ // Falling edge: advance buffer and load next byte
264
+ if (nBuffPos_) {
265
+ nBuffPos_++;
266
+ }
267
+ if (nBuffPos_ == nDataLen_ || nBuffPos_ > 7) {
268
+ nBuffPos_ = 0; // Read complete, ready for next command
269
+ } else {
270
+ ira_ = byBuff_[nBuffPos_]; // Load next response byte
271
+ }
272
+ by6821B_ |= 0x40; // Set BIT6 for next writing
273
+ }
274
+ }
275
+
276
+ // Reflect MCU handshake signals in Port B input register
277
+ irb_ = by6821B_;
278
+ }
279
+
280
+ void MouseCard::onCommand() {
281
+ // Dispatch based on command byte high nibble.
282
+ // Matches AppleWin's CMouseInterface::OnCommand() exactly.
283
+ // nDataLen_ = total bytes in this transaction (command + data).
284
+ // For read-back commands, byBuff_[1..N] are filled with response data.
285
+
286
+ uint8_t cmd = byBuff_[0] & 0xF0;
287
+ lastCommand_ = byBuff_[0];
288
+
289
+ switch (cmd) {
290
+ case CMD_SET:
291
+ // Mode is in the low nibble of the command byte itself
292
+ nDataLen_ = 1;
293
+ mode_ = byBuff_[0] & 0x0F;
294
+ break;
295
+
296
+ case CMD_READ: {
297
+ // Snapshot position, build 5-byte read response
298
+ nDataLen_ = 6; // command + 5 response bytes
299
+
300
+ // Build status: keep only "moved since last read", then add buttons
301
+ uint8_t status = 0;
302
+ if (moved_) status |= STAT_MOVEMENT_SINCE_READMOUSE;
303
+ snapX_ = mouseX_;
304
+ snapY_ = mouseY_;
305
+
306
+ if (lastButton_) status |= STAT_PREV_BUTTON0;
307
+ lastButton_ = mouseButton_;
308
+ if (mouseButton_) status |= STAT_CURR_BUTTON0;
309
+
310
+ byBuff_[1] = static_cast<uint8_t>(snapX_ & 0xFF);
311
+ byBuff_[2] = static_cast<uint8_t>((snapX_ >> 8) & 0xFF);
312
+ byBuff_[3] = static_cast<uint8_t>(snapY_ & 0xFF);
313
+ byBuff_[4] = static_cast<uint8_t>((snapY_ >> 8) & 0xFF);
314
+ byBuff_[5] = status;
315
+
316
+ moved_ = false;
317
+ break;
318
+ }
319
+
320
+ case CMD_SERV: {
321
+ // Return interrupt status, deassert IRQ
322
+ nDataLen_ = 2;
323
+
324
+ uint8_t irqStatus = 0;
325
+ if (moveInterruptPending_) irqStatus |= STAT_INT_MOVEMENT;
326
+ if (buttonInterruptPending_) irqStatus |= STAT_INT_BUTTON;
327
+ if (vblInterruptPending_) irqStatus |= STAT_INT_VBL;
328
+ byBuff_[1] = irqStatus;
329
+
330
+ // Deassert IRQ (pending flags cleared so next event re-triggers)
331
+ vblInterruptPending_ = false;
332
+ moveInterruptPending_ = false;
333
+ buttonInterruptPending_ = false;
334
+ irqActive_ = false;
335
+ cra_ &= 0x3F;
336
+ break;
337
+ }
338
+
339
+ case CMD_CLEAR:
340
+ nDataLen_ = 1;
341
+ nBuffPos_ = 0;
342
+ mouseX_ = 0;
343
+ mouseY_ = 0;
344
+ snapX_ = 0;
345
+ snapY_ = 0;
346
+ lastButton_ = false;
347
+ moved_ = false;
348
+ break;
349
+
350
+ case CMD_POS:
351
+ nDataLen_ = 5; // command + 4 data bytes
352
+ break;
353
+
354
+ case CMD_INIT:
355
+ nDataLen_ = 3; // command + 2 protocol bytes
356
+ byBuff_[1] = 0xFF; // Acknowledgment byte
357
+ break;
358
+
359
+ case CMD_CLAMP:
360
+ nDataLen_ = 5; // command + 4 data bytes
361
+ break;
362
+
363
+ case CMD_HOME:
364
+ nDataLen_ = 1;
365
+ mouseX_ = 0;
366
+ mouseY_ = 0;
367
+ break;
368
+
369
+ default:
370
+ nDataLen_ = 1;
371
+ break;
372
+ }
373
+
374
+ // Preload first response byte into Port A input for firmware to read
375
+ ira_ = byBuff_[1];
376
+ }
377
+
378
+ void MouseCard::onWrite() {
379
+ // Process buffered write data after all expected bytes received.
380
+ // Matches AppleWin's CMouseInterface::OnWrite() data format.
381
+
382
+ switch (byBuff_[0] & 0xF0) {
383
+ case CMD_CLAMP: {
384
+ // Buffer: [cmd, minLo, maxLo, minHi, maxHi]
385
+ // Axis from bit 0 of command: 0=X, 1=Y
386
+ int16_t minVal = static_cast<int16_t>(
387
+ byBuff_[1] | (byBuff_[3] << 8));
388
+ int16_t maxVal = static_cast<int16_t>(
389
+ byBuff_[2] | (byBuff_[4] << 8));
390
+
391
+ if (byBuff_[0] & 1) {
392
+ // Clamp Y
393
+ clampMinY_ = minVal;
394
+ clampMaxY_ = maxVal;
395
+ if (mouseY_ < clampMinY_) mouseY_ = clampMinY_;
396
+ if (mouseY_ > clampMaxY_) mouseY_ = clampMaxY_;
397
+ } else {
398
+ // Clamp X
399
+ clampMinX_ = minVal;
400
+ clampMaxX_ = maxVal;
401
+ if (mouseX_ < clampMinX_) mouseX_ = clampMinX_;
402
+ if (mouseX_ > clampMaxX_) mouseX_ = clampMaxX_;
403
+ }
404
+ break;
405
+ }
406
+
407
+ case CMD_POS:
408
+ // Buffer: [cmd, Xlo, Xhi, Ylo, Yhi]
409
+ mouseX_ = static_cast<int16_t>(
410
+ byBuff_[1] | (byBuff_[2] << 8));
411
+ mouseY_ = static_cast<int16_t>(
412
+ byBuff_[3] | (byBuff_[4] << 8));
413
+ // Apply clamping
414
+ if (mouseX_ < clampMinX_) mouseX_ = clampMinX_;
415
+ if (mouseX_ > clampMaxX_) mouseX_ = clampMaxX_;
416
+ if (mouseY_ < clampMinY_) mouseY_ = clampMinY_;
417
+ if (mouseY_ > clampMaxY_) mouseY_ = clampMaxY_;
418
+ break;
419
+
420
+ case CMD_INIT:
421
+ clampMinX_ = 0;
422
+ clampMaxX_ = 1023;
423
+ clampMinY_ = 0;
424
+ clampMaxY_ = 1023;
425
+ mouseX_ = 0;
426
+ mouseY_ = 0;
427
+ snapX_ = 0;
428
+ snapY_ = 0;
429
+ break;
430
+
431
+ default:
432
+ break;
433
+ }
434
+ }
435
+
436
+ // ============================================================================
437
+ // VBL Interrupt Generation
438
+ // ============================================================================
439
+
440
+ void MouseCard::update(int cycles) {
441
+ if (!cycleCallback_) return;
442
+
443
+ uint64_t totalCycles = cycleCallback_();
444
+
445
+ // Detect VBL transition (scanline 192, start of vertical blank)
446
+ uint64_t cycleInFrame = totalCycles % CYCLES_PER_FRAME;
447
+ int scanline = static_cast<int>(cycleInFrame / CYCLES_PER_SCANLINE);
448
+ bool inVBL = (scanline >= 192);
449
+
450
+ // Detect transition into VBL
451
+ if (inVBL && !wasInVBL_) {
452
+ if (mode_ & MODE_INT_VBL) {
453
+ vblInterruptPending_ = true;
454
+ }
455
+
456
+ // Also check for movement/button interrupts at VBL
457
+ if ((mode_ & MODE_INT_MOVEMENT) && moved_) {
458
+ moveInterruptPending_ = true;
459
+ }
460
+ if ((mode_ & MODE_INT_BUTTON) && buttonChanged_) {
461
+ buttonInterruptPending_ = true;
462
+ buttonChanged_ = false;
463
+ }
464
+
465
+ // Fire IRQ if any interrupt is pending and mouse is enabled
466
+ if ((mode_ & MODE_MOUSE_ON) &&
467
+ (vblInterruptPending_ || moveInterruptPending_ || buttonInterruptPending_)) {
468
+ irqActive_ = true;
469
+ cra_ |= 0x80;
470
+ if (irqCallback_) {
471
+ irqCallback_();
472
+ }
473
+ }
474
+ }
475
+
476
+ wasInVBL_ = inVBL;
477
+ }
478
+
479
+ // ============================================================================
480
+ // Mouse Input
481
+ // ============================================================================
482
+
483
+ void MouseCard::addDelta(int dx, int dy) {
484
+ if (dx == 0 && dy == 0) return;
485
+
486
+ mouseX_ += static_cast<int16_t>(dx);
487
+ mouseY_ += static_cast<int16_t>(dy);
488
+
489
+ // Apply clamping
490
+ if (mouseX_ < clampMinX_) mouseX_ = clampMinX_;
491
+ if (mouseX_ > clampMaxX_) mouseX_ = clampMaxX_;
492
+ if (mouseY_ < clampMinY_) mouseY_ = clampMinY_;
493
+ if (mouseY_ > clampMaxY_) mouseY_ = clampMaxY_;
494
+
495
+ moved_ = true;
496
+ }
497
+
498
+ void MouseCard::setMouseButton(bool pressed) {
499
+ if (pressed != mouseButton_) {
500
+ lastButton_ = mouseButton_;
501
+ mouseButton_ = pressed;
502
+ buttonChanged_ = true;
503
+ }
504
+ }
505
+
506
+ void MouseCard::updateIRQState() {
507
+ // IRQ is active if any PIA IRQ flag is set and the corresponding
508
+ // control register enables it
509
+ bool piaIRQ = (cra_ & 0x80) || (crb_ & 0x80);
510
+ if (!piaIRQ) {
511
+ irqActive_ = false;
512
+ }
513
+ }
514
+
515
+ uint8_t MouseCard::readExpansionROM(uint16_t offset) {
516
+ // The mouse card does not use expansion ROM - all firmware is
517
+ // accessed through banked slot ROM. This should not be called.
518
+ (void)offset;
519
+ return 0xFF;
520
+ }
521
+
522
+ // ============================================================================
523
+ // State Serialization
524
+ // ============================================================================
525
+
526
+ size_t MouseCard::serialize(uint8_t* buffer, size_t maxSize) const {
527
+ if (maxSize < STATE_SIZE) return 0;
528
+
529
+ size_t off = 0;
530
+
531
+ // PIA state (8 bytes)
532
+ buffer[off++] = ddra_;
533
+ buffer[off++] = ddrb_;
534
+ buffer[off++] = ora_;
535
+ buffer[off++] = orb_;
536
+ buffer[off++] = ira_;
537
+ buffer[off++] = irb_;
538
+ buffer[off++] = cra_;
539
+ buffer[off++] = crb_;
540
+
541
+ // Mouse position (8 bytes)
542
+ buffer[off++] = static_cast<uint8_t>(mouseX_ & 0xFF);
543
+ buffer[off++] = static_cast<uint8_t>((mouseX_ >> 8) & 0xFF);
544
+ buffer[off++] = static_cast<uint8_t>(mouseY_ & 0xFF);
545
+ buffer[off++] = static_cast<uint8_t>((mouseY_ >> 8) & 0xFF);
546
+
547
+ // Clamp bounds (8 bytes)
548
+ buffer[off++] = static_cast<uint8_t>(clampMinX_ & 0xFF);
549
+ buffer[off++] = static_cast<uint8_t>((clampMinX_ >> 8) & 0xFF);
550
+ buffer[off++] = static_cast<uint8_t>(clampMaxX_ & 0xFF);
551
+ buffer[off++] = static_cast<uint8_t>((clampMaxX_ >> 8) & 0xFF);
552
+ buffer[off++] = static_cast<uint8_t>(clampMinY_ & 0xFF);
553
+ buffer[off++] = static_cast<uint8_t>((clampMinY_ >> 8) & 0xFF);
554
+ buffer[off++] = static_cast<uint8_t>(clampMaxY_ & 0xFF);
555
+ buffer[off++] = static_cast<uint8_t>((clampMaxY_ >> 8) & 0xFF);
556
+
557
+ // Flags (4 bytes)
558
+ buffer[off++] = (mouseButton_ ? 1 : 0) | (lastButton_ ? 2 : 0)
559
+ | (moved_ ? 4 : 0) | (buttonChanged_ ? 8 : 0);
560
+ buffer[off++] = (irqActive_ ? 1 : 0) | (vblInterruptPending_ ? 2 : 0)
561
+ | (moveInterruptPending_ ? 4 : 0) | (buttonInterruptPending_ ? 8 : 0);
562
+ buffer[off++] = lastCommand_;
563
+ buffer[off++] = mode_;
564
+
565
+ // Slot number (1 byte)
566
+ buffer[off++] = slotNum_;
567
+
568
+ // VBL tracking (1 byte)
569
+ buffer[off++] = wasInVBL_ ? 1 : 0;
570
+
571
+ // Protocol state (4 bytes: by6821B_, byState_, nBuffPos_, nDataLen_)
572
+ buffer[off++] = by6821B_;
573
+ buffer[off++] = byState_;
574
+ buffer[off++] = nBuffPos_;
575
+ buffer[off++] = nDataLen_;
576
+
577
+ // Reserved (to reach STATE_SIZE=36)
578
+ while (off < STATE_SIZE) {
579
+ buffer[off++] = 0;
580
+ }
581
+
582
+ return off;
583
+ }
584
+
585
+ size_t MouseCard::deserialize(const uint8_t* buffer, size_t size) {
586
+ if (size < STATE_SIZE) return 0;
587
+
588
+ size_t off = 0;
589
+
590
+ // PIA state
591
+ ddra_ = buffer[off++];
592
+ ddrb_ = buffer[off++];
593
+ ora_ = buffer[off++];
594
+ orb_ = buffer[off++];
595
+ ira_ = buffer[off++];
596
+ irb_ = buffer[off++];
597
+ cra_ = buffer[off++];
598
+ crb_ = buffer[off++];
599
+
600
+ // Mouse position
601
+ mouseX_ = static_cast<int16_t>(buffer[off] | (buffer[off + 1] << 8));
602
+ off += 2;
603
+ mouseY_ = static_cast<int16_t>(buffer[off] | (buffer[off + 1] << 8));
604
+ off += 2;
605
+
606
+ // Clamp bounds
607
+ clampMinX_ = static_cast<int16_t>(buffer[off] | (buffer[off + 1] << 8));
608
+ off += 2;
609
+ clampMaxX_ = static_cast<int16_t>(buffer[off] | (buffer[off + 1] << 8));
610
+ off += 2;
611
+ clampMinY_ = static_cast<int16_t>(buffer[off] | (buffer[off + 1] << 8));
612
+ off += 2;
613
+ clampMaxY_ = static_cast<int16_t>(buffer[off] | (buffer[off + 1] << 8));
614
+ off += 2;
615
+
616
+ // Flags
617
+ uint8_t flags1 = buffer[off++];
618
+ mouseButton_ = (flags1 & 1) != 0;
619
+ lastButton_ = (flags1 & 2) != 0;
620
+ moved_ = (flags1 & 4) != 0;
621
+ buttonChanged_ = (flags1 & 8) != 0;
622
+
623
+ uint8_t flags2 = buffer[off++];
624
+ irqActive_ = (flags2 & 1) != 0;
625
+ vblInterruptPending_ = (flags2 & 2) != 0;
626
+ moveInterruptPending_ = (flags2 & 4) != 0;
627
+ buttonInterruptPending_ = (flags2 & 8) != 0;
628
+
629
+ lastCommand_ = buffer[off++];
630
+ mode_ = buffer[off++];
631
+
632
+ // Slot number
633
+ slotNum_ = buffer[off++];
634
+
635
+ // VBL tracking
636
+ wasInVBL_ = buffer[off++] != 0;
637
+
638
+ // Protocol state
639
+ by6821B_ = buffer[off++];
640
+ byState_ = buffer[off++];
641
+ nBuffPos_ = buffer[off++];
642
+ nDataLen_ = buffer[off++];
643
+
644
+ // Restore snapshot from current position
645
+ snapX_ = mouseX_;
646
+ snapY_ = mouseY_;
647
+
648
+ // Skip reserved
649
+ off = STATE_SIZE;
650
+
651
+ return off;
652
+ }
653
+
654
+ } // namespace a2e