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,273 @@
1
+ # Softswitch Implementation Comparison: web-a2e vs AppleWin
2
+
3
+ This document compares the softswitch handling between web-a2e and AppleWin (the reference Apple II emulator).
4
+
5
+ ## Architecture Overview
6
+
7
+ | Aspect | web-a2e | AppleWin |
8
+ |--------|---------|----------|
9
+ | **Structure** | Single `SoftSwitches` struct with ~30 boolean flags in `types.hpp`, centralized handling in `MMU::readSoftSwitch()` / `writeSoftSwitch()` | Bitmask `g_memmode` with flags (`MF_80STORE`, `MF_AUXREAD`, etc.), distributed across IO handler functions |
10
+ | **Dispatch** | Single large `switch` statement on `(address & 0xFF)` | Function pointer array (`IORead[8]`, `IOWrite[8]`) dispatching to `IORead_C0xx()` / `IOWrite_C0xx()` handlers |
11
+ | **Memory Model** | Direct boolean flags accessed via `switches_.xxx` | Bitfield operations: `SetMemMode(g_memmode | MF_FLAG)` |
12
+
13
+ ## I/O Dispatch Mechanism
14
+
15
+ ### AppleWin Approach
16
+
17
+ AppleWin uses function pointer arrays split by $10 address ranges:
18
+
19
+ ```cpp
20
+ static iofunction IORead_C0xx[8] = {
21
+ IORead_C00x, // Keyboard
22
+ IORead_C01x, // Memory/Video status
23
+ IORead_C02x, // Cassette
24
+ IORead_C03x, // Speaker
25
+ IORead_C04x, // Unused
26
+ IORead_C05x, // Video mode switches
27
+ IORead_C06x, // Joystick
28
+ IORead_C07x, // Joystick/Video
29
+ };
30
+ ```
31
+
32
+ Each handler function processes 16 addresses:
33
+
34
+ ```cpp
35
+ static BYTE __stdcall IORead_C01x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
36
+ {
37
+ bool res = false;
38
+ switch (addr & 0xf)
39
+ {
40
+ case 0x0: return KeybReadFlag();
41
+ case 0x1: res = SW_BANK2 ? true : false; break;
42
+ case 0x2: res = SW_HIGHRAM ? true : false; break;
43
+ // ...
44
+ }
45
+ return KeybGetKeycode() | (res ? 0x80 : 0);
46
+ }
47
+ ```
48
+
49
+ ### web-a2e Approach
50
+
51
+ web-a2e uses a monolithic switch in `mmu.cpp`:
52
+
53
+ ```cpp
54
+ uint8_t MMU::readSoftSwitch(uint16_t address) {
55
+ uint8_t reg = address & 0xFF;
56
+ switch (reg) {
57
+ case 0x00: return keyboardLatch_;
58
+ case 0x10: keyboardLatch_ &= 0x7F; return keyboardLatch_;
59
+ case 0x11: return (switches_.lcram2 ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F);
60
+ // ... all 256 cases
61
+ }
62
+ }
63
+ ```
64
+
65
+ **Trade-offs:**
66
+ - AppleWin: Better code organization, easier to maintain per-device handlers
67
+ - web-a2e: Simpler dispatch, all logic visible in one place
68
+
69
+ ## Status Read Return Values ($C01x)
70
+
71
+ Both implementations return the switch state in bit 7 with data bus noise in bits 0-6.
72
+
73
+ ### AppleWin
74
+
75
+ Returns the last keyboard keycode OR'd with the status bit:
76
+
77
+ ```cpp
78
+ return KeybGetKeycode() | (res ? 0x80 : 0);
79
+ ```
80
+
81
+ ### web-a2e
82
+
83
+ Returns floating bus value OR'd with the status bit:
84
+
85
+ ```cpp
86
+ return (switches_.lcram2 ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F);
87
+ ```
88
+
89
+ Both approaches are correct per "Understanding the Apple IIe" (UTAIIe) - the lower 7 bits represent data bus noise from the previous cycle.
90
+
91
+ ## Memory Paging Updates
92
+
93
+ ### AppleWin
94
+
95
+ AppleWin maintains shadow memory arrays for fast access:
96
+
97
+ ```cpp
98
+ LPBYTE memshadow[256]; // Read pointers per 256-byte page
99
+ LPBYTE memwrite[256]; // Write pointers per 256-byte page
100
+ BYTE memdirty[256]; // Dirty flags for copy-back
101
+ ```
102
+
103
+ When a paging mode changes, `UpdatePaging()` copies memory between shadow regions:
104
+
105
+ ```cpp
106
+ if ((lastmemmode != g_memmode) || modechanging)
107
+ {
108
+ for (UINT page = 0; page < 256; page++)
109
+ {
110
+ if (oldshadow[page] != memshadow[page])
111
+ {
112
+ if (*(memdirty+page) & 1)
113
+ memcpy(oldshadow[page], mem+(page << 8), 256);
114
+ memcpy(mem+(page << 8), memshadow[page], 256);
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ ### web-a2e
121
+
122
+ web-a2e evaluates switches at memory access time without shadow caching:
123
+
124
+ ```cpp
125
+ // In read() method
126
+ if (switches_.store80 && switches_.page2) {
127
+ return auxMemory_[address];
128
+ }
129
+ if (switches_.ramrd) {
130
+ return auxMemory_[address];
131
+ }
132
+ return mainMemory_[address];
133
+ ```
134
+
135
+ **Trade-offs:**
136
+ - AppleWin: Faster memory access after mode change (direct pointer dereference)
137
+ - web-a2e: Simpler implementation, no synchronization overhead, but more conditionals per access
138
+
139
+ ## Language Card Handling ($C080-$C08F)
140
+
141
+ ### AppleWin
142
+
143
+ Delegates to a separate `LanguageCard` class that manages:
144
+ - Bank selection (bank 1 vs bank 2)
145
+ - Read source (RAM vs ROM)
146
+ - Write enable state with pre-write tracking
147
+
148
+ ### web-a2e
149
+
150
+ Handles inline in `handleLanguageCardSwitch()`:
151
+
152
+ ```cpp
153
+ uint8_t MMU::handleLanguageCardSwitch(uint8_t reg) {
154
+ bool bank2 = !(reg & 0x08);
155
+ uint8_t op = reg & 0x03;
156
+ bool readRAM = (op == 0 || op == 3);
157
+
158
+ switch (op) {
159
+ case 0: // $C080, $C088: Read RAM, write disabled
160
+ switches_.lcwrite = false;
161
+ switches_.lcprewrite = false;
162
+ break;
163
+ case 1: // $C081, $C089: Read ROM, write enable on second read
164
+ if (switches_.lcprewrite) {
165
+ switches_.lcwrite = true;
166
+ }
167
+ switches_.lcprewrite = true;
168
+ break;
169
+ // ...
170
+ }
171
+ switches_.lcram = readRAM;
172
+ switches_.lcram2 = bank2;
173
+ return getFloatingBusValue();
174
+ }
175
+ ```
176
+
177
+ Both implementations correctly handle the double-read requirement for write enable.
178
+
179
+ ## Annunciators ($C058-$C05F)
180
+
181
+ ### AppleWin
182
+
183
+ Has special handling for DHIRES that checks machine type and IOUDIS state:
184
+
185
+ ```cpp
186
+ static BYTE __stdcall IOReadWrite_ANx(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
187
+ {
188
+ if (IsAppleIIeOrAbove(GetApple2Type()))
189
+ {
190
+ if (!IsAppleIIc(GetApple2Type()) || SW_IOUDIS)
191
+ GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
192
+ }
193
+ if (IsAppleIIc(GetApple2Type()))
194
+ return 0; // No ANx lines for //c
195
+ return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles);
196
+ }
197
+ ```
198
+
199
+ ### web-a2e
200
+
201
+ Directly sets annunciator flags without DHIRES coupling:
202
+
203
+ ```cpp
204
+ case 0x5E:
205
+ switches_.an3 = false;
206
+ return getFloatingBusValue(); // AN3 OFF = DHIRES enabled
207
+ case 0x5F:
208
+ switches_.an3 = true;
209
+ return getFloatingBusValue(); // AN3 ON = DHIRES disabled
210
+ ```
211
+
212
+ The DHIRES state is derived from AN3 when needed rather than tracked separately.
213
+
214
+ ## Potential Issues in web-a2e
215
+
216
+ ### 1. $C000-$C00B Read Behavior
217
+
218
+ web-a2e treats $C000 as the only keyboard read address. AppleWin's `IORead_C00x` returns keyboard data for the entire `$C000-$C00F` range, which matches hardware behavior where the keyboard decoder responds to all 16 addresses.
219
+
220
+ **Current web-a2e code:**
221
+ ```cpp
222
+ case 0x00: return keyboardLatch_;
223
+ case 0x01: case 0x02: ... case 0x0F: return getFloatingBusValue();
224
+ ```
225
+
226
+ **Should be:**
227
+ ```cpp
228
+ case 0x00: case 0x01: case 0x02: ... case 0x0F:
229
+ return keyboardLatch_;
230
+ ```
231
+
232
+ ### 2. Write-Only Switch Reads ($C000-$C00B)
233
+
234
+ AppleWin returns keyboard data when reading write-only memory switches. web-a2e returns floating bus, which may cause compatibility issues with software that reads these addresses expecting keyboard data.
235
+
236
+ ### 3. PAGE2/HIRES and Memory Remapping
237
+
238
+ AppleWin routes $C054-$C057 through `MemSetPaging()` which triggers `UpdatePaging()` to remap memory pointers. web-a2e sets switches but relies on runtime evaluation. This is functionally equivalent but should be verified for timing-sensitive software.
239
+
240
+ ### 4. 80STORE/PAGE2 Interaction
241
+
242
+ AppleWin explicitly updates memory mappings when PAGE2 changes and 80STORE is set:
243
+
244
+ ```cpp
245
+ if (SW_80STORE)
246
+ {
247
+ for (loop = 0x04; loop < 0x08; loop++)
248
+ {
249
+ memshadow[loop] = SW_PAGE2 ? memaux+(loop << 8) : memmain+(loop << 8);
250
+ memwrite[loop] = mem+(loop << 8);
251
+ }
252
+ }
253
+ ```
254
+
255
+ web-a2e checks `store80 && page2` at read/write time, which is functionally equivalent but potentially slower for tight loops.
256
+
257
+ ## Summary
258
+
259
+ | Feature | web-a2e | AppleWin |
260
+ |---------|---------|----------|
261
+ | Code clarity | Better - all logic in one switch | Distributed across handlers |
262
+ | Performance | Slower - runtime switch evaluation | Faster - pointer tables |
263
+ | Machine support | IIe focused | II, II+, IIe, IIc, IIgs |
264
+ | Memory model | Direct evaluation | Shadow page tables |
265
+ | Maintainability | Easier - single file | Harder - multiple classes |
266
+
267
+ web-a2e's implementation is cleaner and more readable with the struct-based approach, while AppleWin is more optimized for performance with its paged shadow memory and function pointer dispatch. Both correctly implement the core Apple IIe softswitch semantics.
268
+
269
+ ## References
270
+
271
+ - AppleWin source: https://github.com/AppleWin/AppleWin/blob/master/source/Memory.cpp
272
+ - "Understanding the Apple IIe" by James Sather (UTAIIe)
273
+ - Apple IIe Technical Reference Manual
@@ -0,0 +1,89 @@
1
+ # Thunderclock Debug Notes
2
+
3
+ ## Current Status
4
+
5
+ - ProDOS **detects** the clock card (MACHID bit 0 is set)
6
+ - But new files show `<NO DATE>` - time data isn't being read correctly
7
+ - The serial protocol implementation likely needs adjustment
8
+
9
+ ## Verification Commands (ProDOS BASIC)
10
+
11
+ ### Check if clock was detected
12
+ ```
13
+ PRINT PEEK(49048)
14
+ ```
15
+ - Odd number = clock detected (bit 0 set)
16
+ - Result was **179** - clock IS detected
17
+
18
+ ### Check clock driver hook
19
+ ```
20
+ PRINT PEEK(49926)
21
+ ```
22
+ - 76 ($4C) = JMP, clock driver installed
23
+ - 96 ($60) = RTS, no clock driver
24
+ - Result was **144** ($90) - unexpected value
25
+
26
+ ### Test I/O register (slot 5)
27
+ ```
28
+ PRINT PEEK(49360)
29
+ ```
30
+ - Reads $C0D0 (Thunderclock I/O for slot 5)
31
+ - Result was **0** - card is responding
32
+
33
+ ## Manual Serial Protocol Test
34
+
35
+ Run these in order to test if the Thunderclock responds to the serial protocol:
36
+
37
+ ### Step 1 - Trigger time read (STROBE + CMD_TIMED)
38
+ ```
39
+ POKE 49360, 164
40
+ ```
41
+ ($A4 = CMD_TIMED $A0 | STROBE $04)
42
+
43
+ ### Step 2 - Clock out first data bit
44
+ ```
45
+ POKE 49360, 162
46
+ ```
47
+ ($A2 = CMD_TIMED $A0 | CLOCK $02)
48
+
49
+ ### Step 3 - Read the result
50
+ ```
51
+ PRINT PEEK(49360)
52
+ ```
53
+ - If bit 7 is set: value >= 128
54
+ - If bit 7 is clear: value < 128
55
+ - This tells us if data bits are being shifted out
56
+
57
+ ### Step 4 - Clock out more bits and check pattern
58
+ Repeat steps 2-3 multiple times to see if different bits come out:
59
+ ```
60
+ POKE 49360, 160: POKE 49360, 162: PRINT PEEK(49360)
61
+ POKE 49360, 160: POKE 49360, 162: PRINT PEEK(49360)
62
+ POKE 49360, 160: POKE 49360, 162: PRINT PEEK(49360)
63
+ ```
64
+ (Each line: clear clock, set clock, read bit)
65
+
66
+ ## Expected Time Format
67
+
68
+ The Thunderclock outputs 40 bits in this order (MSB first):
69
+ 1. Month (4 bits) - 0-11
70
+ 2. Day of week (4 bits) - 0-6, Sunday=0
71
+ 3. Day of month (8 bits BCD)
72
+ 4. Hour (8 bits BCD, 24-hour)
73
+ 5. Minute (8 bits BCD)
74
+ 6. Second (8 bits BCD)
75
+
76
+ ## Memory Addresses Reference
77
+
78
+ | Address | Decimal | Purpose |
79
+ |---------|---------|---------|
80
+ | $C0D0 | 49360 | Slot 5 I/O register |
81
+ | $C500 | 50432 | Slot 5 ROM start |
82
+ | $BF98 | 49048 | ProDOS MACHID |
83
+ | $BF06 | 49926 | ProDOS clock hook |
84
+
85
+ ## Files
86
+
87
+ - `src/core/cards/thunderclock_card.cpp` - Serial protocol implementation
88
+ - `src/core/cards/thunderclock_card.hpp` - Card class definition
89
+ - `roms/Thunderclock Plus ROM.bin` - Real Thunderclock ROM (2KB)
@@ -0,0 +1,72 @@
1
+ 10 HGR : HGR2
2
+ 12 SZ = 150 : CX = 140 : CY = 96
3
+ 15 SC = 0
4
+ 18 ANGLE = 0
5
+ 20 DIM PTS(7,2)
6
+ 25 DIM RZP(7,2) : DIM RYP(7,2) : DIM RXP(7,2)
7
+ 27 DIM PRJ(7,2)
8
+ 30 GOSUB 5000
9
+
10
+ 100 REM DRAW LOOP
11
+ 105 CALL 62450
12
+ 110 GOSUB 2000 : REM ROTATE POINTS
13
+ 120 GOSUB 6000 : REM LINES
14
+ 140 ANGLE = ANGLE + 0.1
15
+ 150 GOSUB 1000
16
+ 160 GOTO 100
17
+
18
+ 500 X1 = PRJ(A,0) * SZ + CX
19
+ 510 Y1 = PRJ(A,1) * SZ + CY
20
+ 520 X2 = PRJ(B,0) * SZ + CX
21
+ 530 Y2 = PRJ(B,1) * SZ + CY
22
+ 535 HCOLOR=3
23
+ 540 HPLOT X1,Y1 TO X2,Y2
24
+ 550 RETURN
25
+
26
+ 1000 SC = NOT SC
27
+ 1010 POKE 49236 + NOT SC,0
28
+ 1020 POKE 230,32 + 32 * SC
29
+ 1030 RETURN
30
+
31
+ 2000 CS = COS(ANGLE)
32
+ 2010 SN = SIN(ANGLE)
33
+ 2020 FOR N = 0 TO 7
34
+ 2030 RZP(N,0) = -CS * PTS(N,0) + SN * PTS(N,1) + 0 * PTS(N,2)
35
+ 2040 RZP(N,1) = SN * PTS(N,0) + CS * PTS(N,1) + 0 * PTS(N,2)
36
+ 2050 RZP(N,2) = 0 * PTS(N,0) + 0 * PTS(N,1) + 1 * PTS(N,2)
37
+ 2060 RYP(N,0) = CS * RZP(N,0) + 0 * RZP(N,1) + SN * RZP(N,2)
38
+ 2070 RYP(N,1) = 0 * RZP(N,0) + 1 * RZP(N,1) + 0 * RZP(N,2)
39
+ 2080 RYP(N,2) = -SN * RZP(N,0) + 0 * RZP(N,1) + CS * RZP(N,2)
40
+ 2090 RXP(N,0) = RYP(N,0)
41
+ 2100 RXP(N,1) = CS * RYP(N,1) - SN * RYP(N,2)
42
+ 2110 RXP(N,2) = SN * RYP(N,1) + CS * RYP(N,2)
43
+ 2120 Z = 1 / (5 - RXP(N,2))
44
+ 2130 PRJ(N,0) = RXP(N,0) * Z
45
+ 2140 PRJ(N,1) = RXP(N,1) * Z
46
+ 2150 NEXT N
47
+ 2160 RETURN
48
+
49
+ 5000 PTS(0,0) = -1: PTS(0,1) = -1 : PTS(0,2) = -1
50
+ 5010 PTS(1,0) = 1: PTS(1,1) = -1 : PTS(1,2) = -1
51
+ 5020 PTS(2,0) = 1: PTS(2,1) = 1 : PTS(2,2) = -1
52
+ 5030 PTS(3,0) = -1: PTS(3,1) = 1 : PTS(3,2) = -1
53
+ 5040 PTS(4,0) = -1: PTS(4,1) = -1 : PTS(4,2) = 1
54
+ 5050 PTS(5,0) = 1: PTS(5,1) = -1 : PTS(5,2) = 1
55
+ 5060 PTS(6,0) = 1: PTS(6,1) = 1 : PTS(6,2) = 1
56
+ 5070 PTS(7,0) = -1: PTS(7,1) = 1 : PTS(7,2) = 1
57
+ 5080 RETURN
58
+
59
+ 6000 REM DRAW LINES
60
+ 6010 A = 0 : B = 1 : GOSUB 500
61
+ 6020 A = 1 : B = 2 : GOSUB 500
62
+ 6030 A = 2 : B = 3 : GOSUB 500
63
+ 6040 A = 3 : B = 0 : GOSUB 500
64
+ 6050 A = 4 : B = 5 : GOSUB 500
65
+ 6060 A = 5 : B = 6 : GOSUB 500
66
+ 6070 A = 6 : B = 7 : GOSUB 500
67
+ 6080 A = 7 : B = 4 : GOSUB 500
68
+ 6090 A = 0 : B = 4 : GOSUB 500
69
+ 6100 A = 1 : B = 5 : GOSUB 500
70
+ 6110 A = 2 : B = 6 : GOSUB 500
71
+ 6120 A = 3 : B = 7 : GOSUB 500
72
+ 6130 RETURN
@@ -0,0 +1,55 @@
1
+ **********************************
2
+ * *
3
+ * HELLO WORLD - APPLE IIE *
4
+ * 65C02 ASSEMBLY EXAMPLE *
5
+ * *
6
+ **********************************
7
+
8
+ * Apple IIe ROM routines
9
+ COUT EQU $FDED ;Character output routine
10
+
11
+ ORG $0800 ;Standard BASIC program area
12
+
13
+ **********************************
14
+ * Main program entry point
15
+ **********************************
16
+ START LDX #$00 ;Initialize index to 0
17
+
18
+ **********************************
19
+ * Print loop - one char at a time
20
+ **********************************
21
+ LOOP LDA MESSAGE,X ;Load character at MESSAGE+X
22
+ BEQ DONE ;If zero (end of string), we're done
23
+ JSR COUT ;Print the character
24
+ PHX ;Save X on stack (65C02)
25
+ JSR DELAY ;Wait a bit
26
+ PLX ;Restore X from stack (65C02)
27
+ INX ;Increment index
28
+ BNE LOOP ;Continue if X hasn't wrapped
29
+
30
+ **********************************
31
+ * End of program
32
+ **********************************
33
+ DONE RTS ;Return to caller
34
+
35
+ **********************************
36
+ * Delay routine - nested loop
37
+ * Adjust WAIT value for longer/shorter delay
38
+ **********************************
39
+ WAIT EQU $60 ;Delay multiplier (~100ms)
40
+
41
+ DELAY LDY #WAIT ;Outer loop counter
42
+ DLY1 LDX #$FF ;Inner loop counter
43
+ DLY2 DEX ;Decrement inner
44
+ BNE DLY2 ;Loop until X=0
45
+ DEY ;Decrement outer
46
+ BNE DLY1 ;Loop until Y=0
47
+ RTS
48
+
49
+ **********************************
50
+ * Message data
51
+ * Using " delimiter sets high bit automatically
52
+ **********************************
53
+ MESSAGE ASC "HELLO APPLE IIE!"
54
+ DFB $8D ;Carriage return (with high bit)
55
+ DFB $00 ;Null terminator
@@ -0,0 +1,140 @@
1
+ ; scroll.s - Smooth horizontal text scroller for Apple IIe
2
+ ;
3
+ ; Scrolls a message across the top line of the 40-column text screen.
4
+ ; The message slides in from the right, crosses the screen, and exits left
5
+ ; before looping. Press any key to quit.
6
+ ;
7
+ ; Assemble with Merlin or ca65:
8
+ ; ca65 scroll.s -o scroll.o && ld65 scroll.o -t none -o SCROLL -S 0x0300
9
+ ;
10
+ ; Run from the monitor: 300G
11
+ ; Or BRUN from DOS/ProDOS after saving.
12
+
13
+ ORG $0300
14
+
15
+ ; ── Zero page temporaries ──────────────────────────────────────────────
16
+
17
+ OFFSET EQU $06 ; current scroll offset (2 bytes)
18
+ SRCPTR EQU $08 ; pointer into message buffer (2 bytes)
19
+
20
+ ; ── Hardware / ROM addresses ───────────────────────────────────────────
21
+
22
+ TXTLINE0 EQU $0400 ; screen RAM for text line 0
23
+ KBDSTROBE EQU $C000 ; keyboard data (bit 7 = key ready)
24
+ KBDCLEAR EQU $C010 ; clear keyboard strobe
25
+ HOME EQU $FC58 ; ROM: clear screen and home cursor
26
+
27
+ ; ── Constants ──────────────────────────────────────────────────────────
28
+
29
+ SCRWIDTH EQU 40 ; visible columns
30
+ SPEED EQU $18 ; delay — lower = faster
31
+
32
+ ; ======================================================================
33
+ ; Entry — jump over data block so BUFLEN is defined before use
34
+ ; ======================================================================
35
+
36
+ JMP START
37
+
38
+ ; ======================================================================
39
+ ; Message buffer
40
+ ;
41
+ ; Padded with 40 spaces on each side so the text scrolls cleanly on and
42
+ ; off screen. Characters have the high bit set for normal Apple II text
43
+ ; (ASCII + $80).
44
+ ; ======================================================================
45
+
46
+ BUFFER
47
+ ; 40 leading spaces
48
+ HEX A0A0A0A0A0A0A0A0A0A0 ; 10
49
+ HEX A0A0A0A0A0A0A0A0A0A0 ; 20
50
+ HEX A0A0A0A0A0A0A0A0A0A0 ; 30
51
+ HEX A0A0A0A0A0A0A0A0A0A0 ; 40
52
+
53
+ ; Message: "HELLO FROM THE APPLE //E EMULATOR! "
54
+ HEX C8C5CCCCCF ; HELLO
55
+ HEX A0 ; (space)
56
+ HEX C6D2CFCD ; FROM
57
+ HEX A0 ; (space)
58
+ HEX D4C8C5 ; THE
59
+ HEX A0 ; (space)
60
+ HEX C1D0D0CCC5 ; APPLE
61
+ HEX A0AFAFC5 ; (space)//E
62
+ HEX A0 ; (space)
63
+ HEX C5CDD5CCC1D4CFD2 ; EMULATOR
64
+ HEX A1 ; !
65
+ HEX A0A0A0 ; (trailing spaces before pad)
66
+
67
+ MSGLEN EQU *-BUFFER-40 ; length of message + trailing spaces
68
+
69
+ ; 40 trailing spaces
70
+ HEX A0A0A0A0A0A0A0A0A0A0 ; 10
71
+ HEX A0A0A0A0A0A0A0A0A0A0 ; 20
72
+ HEX A0A0A0A0A0A0A0A0A0A0 ; 30
73
+ HEX A0A0A0A0A0A0A0A0A0A0 ; 40
74
+
75
+ BUFEND
76
+ BUFLEN EQU BUFEND-BUFFER-SCRWIDTH+1 ; number of scroll positions
77
+
78
+ ; ======================================================================
79
+ ; Main code
80
+ ; ======================================================================
81
+
82
+ START JSR HOME ; clear screen
83
+
84
+ LDA #0 ; reset scroll offset
85
+ STA OFFSET
86
+ STA OFFSET+1
87
+
88
+ ; ── Main loop ─────────────────────────────────────────────────────────
89
+
90
+ LOOP LDA OFFSET ; SRCPTR = BUFFER + OFFSET
91
+ CLC
92
+ ADC #<BUFFER
93
+ STA SRCPTR
94
+ LDA OFFSET+1
95
+ ADC #>BUFFER
96
+ STA SRCPTR+1
97
+
98
+ ; Copy 40 characters from SRCPTR to screen line 0
99
+
100
+ LDY #SCRWIDTH-1
101
+ COPY LDA (SRCPTR),Y
102
+ STA TXTLINE0,Y
103
+ DEY
104
+ BPL COPY
105
+
106
+ ; Advance the offset; wrap when we reach the end of the padded message
107
+
108
+ INC OFFSET
109
+ BNE NOWRAP
110
+ INC OFFSET+1
111
+ NOWRAP
112
+ LDA OFFSET+1 ; compare OFFSET with BUFLEN
113
+ CMP #>BUFLEN
114
+ BCC NODELAY ; high byte less — not at end
115
+ BNE DORESET ; high byte greater — past end
116
+ LDA OFFSET
117
+ CMP #<BUFLEN
118
+ BCC NODELAY ; low byte < BUFLEN — continue
119
+ DORESET LDA #0 ; wrap offset back to zero
120
+ STA OFFSET
121
+ STA OFFSET+1
122
+ JMP LOOP
123
+ NODELAY
124
+
125
+ ; ── Frame delay ───────────────────────────────────────────────────────
126
+
127
+ LDX #SPEED
128
+ DELAYOUTER LDY #0
129
+ DELAYINNER DEY
130
+ BNE DELAYINNER
131
+ DEX
132
+ BNE DELAYOUTER
133
+
134
+ ; ── Check for keypress (quit on any key) ──────────────────────────────
135
+
136
+ LDA KBDSTROBE
137
+ BPL LOOP ; no key — keep scrolling
138
+ STA KBDCLEAR ; clear strobe
139
+ RTS ; return to caller / monitor
140
+
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "web-a2e",
3
+ "version": "1.0.0",
4
+ "description": "Apple //e emulator for the browser",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "npm run build:wasm && vite build",
9
+ "build:wasm": "mkdir -p build && cd build && emcmake cmake .. && emmake make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu)",
10
+ "preview": "vite preview",
11
+ "clean": "rm -rf build dist public/a2e.js public/a2e.wasm",
12
+ "deploy": "rsync -avz --delete dist/ vps-mike:web-a2e/data/",
13
+ "deploy:staging": "rsync -avz --delete dist/ vps-mike:/home/mike/web-a2e-staging/data/"
14
+ },
15
+ "devDependencies": {
16
+ "vite": "^7.3.1"
17
+ }
18
+ }
Binary file
Binary file
Binary file
Binary file