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,372 @@
1
+ # AI Agent
2
+
3
+ The AI Agent integration allows LLMs like Claude to control the emulator through natural language commands. The agent can show/hide windows, manage disks, read/write BASIC programs, and inspect emulator state in real time using the AG-UI protocol over an MCP server.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Connection Status](#connection-status)
10
+ - [Setting Up the MCP Server](#setting-up-the-mcp-server)
11
+ - [Example Prompts](#example-prompts)
12
+ - [Window Management](#window-management)
13
+ - [Disk Management](#disk-management)
14
+ - [SmartPort Hard Drives](#smartport-hard-drives)
15
+ - [Slot Configuration](#slot-configuration)
16
+ - [BASIC Programs](#basic-programs)
17
+ - [Assembly Programs](#assembly-programs)
18
+ - [Memory Operations](#memory-operations)
19
+
20
+ ---
21
+
22
+ ## Connection Status
23
+
24
+ The agent connection status is shown by a sparkle icon in the toolbar header:
25
+
26
+ | Icon | Status | Description |
27
+ |------|--------|-------------|
28
+ | <svg viewBox="0 0 24 24" width="20" height="20" fill="#6e7681"><path d="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5L12 2zM6 16l.75 2.25L9 19l-2.25.75L6 22l-.75-2.25L3 19l2.25-.75L6 16zM18 16l.75 2.25L21 19l-2.25.75L18 22l-.75-2.25L15 19l2.25-.75L18 16z"/></svg> | **Disconnected** | MCP server is not running or not reachable |
29
+ | <svg viewBox="0 0 24 24" width="20" height="20" fill="#FDBE34"><path d="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5L12 2zM6 16l.75 2.25L9 19l-2.25.75L6 22l-.75-2.25L3 19l2.25-.75L6 16zM18 16l.75 2.25L21 19l-2.25.75L18 22l-.75-2.25L15 19l2.25-.75L18 16z"/></svg> | **Connected** | Agent is connected and ready to receive commands |
30
+ | <svg viewBox="0 0 24 24" width="20" height="20" fill="#E5504F"><path d="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5L12 2zM6 16l.75 2.25L9 19l-2.25.75L6 22l-.75-2.25L3 19l2.25-.75L6 16zM18 16l.75 2.25L21 19l-2.25.75L18 22l-.75-2.25L15 19l2.25-.75L18 16z"/></svg> | **Interrupted** | Connection error or server unavailable |
31
+
32
+ Click the sparkle icon to open the agent connection panel and view detailed status information.
33
+
34
+ ---
35
+
36
+ ## Setting Up the MCP Server
37
+
38
+ The AI Agent uses the Model Context Protocol (MCP) to communicate with LLM clients like Claude Code. Configure your MCP client to connect to the emulator's agent server.
39
+
40
+ ### Configuration
41
+
42
+ Add the following to your MCP configuration file (e.g., `~/.claude.json` or `.mcp.json` in your project):
43
+
44
+ **Using bunx (recommended):**
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "appleii-agent": {
50
+ "type": "stdio",
51
+ "command": "bunx",
52
+ "args": [
53
+ "-y",
54
+ "@retrotech71/appleii-agent"
55
+ ]
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ **Using npx:**
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "appleii-agent": {
67
+ "type": "stdio",
68
+ "command": "npx",
69
+ "args": [
70
+ "-y",
71
+ "@retrotech71/appleii-agent"
72
+ ]
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ No installation required — the agent is downloaded and run automatically on first use. `bunx` ([Bun](https://bun.sh)) is recommended as it is more likely to work out of the box; `npx` (Node.js) may experience timeout issues on first run while downloading the package.
79
+
80
+ ### How It Works
81
+
82
+ The MCP server starts automatically when your MCP client (e.g., Claude Code) connects. It listens on `http://localhost:3033` and uses the AG-UI protocol to communicate with the emulator frontend running in your browser.
83
+
84
+ ### Port Conflict Management
85
+
86
+ The MCP server includes graceful port conflict handling when multiple instances attempt to use port 3033:
87
+
88
+ - **Automatic Detection:** When port 3033 is already in use, the MCP server stays alive without failing
89
+ - **Status Reporting:** The `server_control` tool reports port conflicts and provides guidance
90
+ - **Port Reclamation:** Any instance can take over the port by shutting down the other instance and starting itself
91
+ - **Two-Step Process:**
92
+ 1. Call `shutdown_remote_server` to stop the other instance
93
+ 2. Call `server_control` with action `start` to start this instance
94
+
95
+ **Example workflow to reclaim port 3033:**
96
+
97
+ ```
98
+ Check the status → port shows as in use
99
+ Shutdown the remote server on port 3033
100
+ Start this server on port 3033
101
+ ```
102
+
103
+ This allows multiple Claude Code sessions or MCP instances to coordinate gracefully without manual process management.
104
+
105
+ ---
106
+
107
+ ## Example Prompts
108
+
109
+ ### Window Management
110
+
111
+ **Show a window:**
112
+ ```
113
+ Show the CPU debugger window
114
+ ```
115
+
116
+ **Hide a window:**
117
+ ```
118
+ Hide the disk drives window
119
+ ```
120
+
121
+ **Focus a window:**
122
+ ```
123
+ Bring the BASIC program window to the front
124
+ ```
125
+
126
+ ### Disk Management
127
+
128
+ **Insert a disk from the filesystem:**
129
+ ```
130
+ Load ~/Documents/Apple_II/ProDOS_2_4_2.dsk into drive 1
131
+ ```
132
+
133
+ **List recent disks:**
134
+ ```
135
+ What disks are in the recent list for drive 1?
136
+ ```
137
+
138
+ **Load from recent disks:**
139
+ ```
140
+ Insert the disk named "Zork_1.dsk" from recent disks into drive 2
141
+ ```
142
+
143
+ **Eject a disk:**
144
+ ```
145
+ Eject the disk from drive 1
146
+ ```
147
+
148
+ **Clear recent disks:**
149
+ ```
150
+ Clear all recent disks for drive 1
151
+ ```
152
+
153
+ ### SmartPort Hard Drives
154
+
155
+ **Insert an image from the filesystem:**
156
+ ```
157
+ Load ~/Images/Total_Replay.hdv into SmartPort device 1
158
+ ```
159
+
160
+ **List recent images:**
161
+ ```
162
+ What images are in the recent list for SmartPort device 1?
163
+ ```
164
+
165
+ **Load from recent images:**
166
+ ```
167
+ Insert Apple Pascal from recent SmartPort images
168
+ ```
169
+
170
+ **Clear recent images:**
171
+ ```
172
+ Clear the recent images list for SmartPort device 1
173
+ ```
174
+
175
+ **Eject an image:**
176
+ ```
177
+ Eject the SmartPort image from device 1
178
+ ```
179
+
180
+ ### Slot Configuration
181
+
182
+ **List all slots:**
183
+ ```
184
+ Show me the current expansion slot configuration
185
+ ```
186
+
187
+ **Install a card:**
188
+ ```
189
+ Install the Mockingboard in slot 4
190
+ ```
191
+
192
+ **Remove a card:**
193
+ ```
194
+ Remove the card from slot 5
195
+ ```
196
+
197
+ **Move a card:**
198
+ ```
199
+ Move the SmartPort card from slot 7 to slot 5
200
+ ```
201
+
202
+ ### BASIC Programs
203
+
204
+ **Read BASIC program from memory:**
205
+ ```
206
+ Load the BASIC program from memory and show it in the editor
207
+ ```
208
+
209
+ **Write BASIC program to memory:**
210
+ ```
211
+ Write this BASIC program to emulator memory:
212
+ 10 PRINT "HELLO, WORLD!"
213
+ 20 GOTO 10
214
+ ```
215
+
216
+ **Get program listing:**
217
+ ```
218
+ What BASIC program is currently in memory?
219
+ ```
220
+
221
+ **Save program to file:**
222
+ ```
223
+ Save the BASIC program from the editor to ~/Documents/myprogram.bas
224
+ ```
225
+
226
+ **Save program from memory to file:**
227
+ ```
228
+ Save the BASIC program from memory to ~/Documents/myprogram.bas
229
+ ```
230
+
231
+ **Set a breakpoint:**
232
+ ```
233
+ Set a breakpoint on BASIC line 20
234
+ ```
235
+
236
+ **Set a statement-level breakpoint:**
237
+ ```
238
+ Set a breakpoint on line 10, second statement
239
+ ```
240
+
241
+ **List all breakpoints:**
242
+ ```
243
+ Show me all BASIC breakpoints
244
+ ```
245
+
246
+ **Remove a breakpoint:**
247
+ ```
248
+ Remove the breakpoint from line 20
249
+ ```
250
+
251
+ **Step to next line:**
252
+ ```
253
+ Step to the next BASIC line
254
+ ```
255
+
256
+ **Get current line number:**
257
+ ```
258
+ What line is the BASIC program stopped at?
259
+ ```
260
+
261
+ **Inspect variables:**
262
+ ```
263
+ Show me all BASIC variables
264
+ ```
265
+
266
+ **Get specific variable value:**
267
+ ```
268
+ What's the value of variable X?
269
+ ```
270
+
271
+ **Set a variable:**
272
+ ```
273
+ Set variable X to 42
274
+ ```
275
+
276
+ **Set a string variable:**
277
+ ```
278
+ Set A$ to "HELLO WORLD"
279
+ ```
280
+
281
+ **Debug workflow example:**
282
+ ```
283
+ Write this program to memory:
284
+ 10 X=1:Y=2:PRINT X+Y
285
+ 20 PRINT "LINE 20"
286
+ 30 END
287
+
288
+ Set a breakpoint on line 10, second statement
289
+ Run the program
290
+ Step to the next line
291
+ Show me all variables
292
+ ```
293
+
294
+ ### Assembly Programs
295
+
296
+ **Get assembly status:**
297
+ ```
298
+ What's the status of the assembler?
299
+ ```
300
+
301
+ **Execute assembled program:**
302
+ ```
303
+ Run the assembled program
304
+ ```
305
+
306
+ **Execute at specific address:**
307
+ ```
308
+ Execute the code at $0800
309
+ ```
310
+
311
+ **Execute with return to BASIC:**
312
+ ```
313
+ Execute $0800 and return to BASIC
314
+ ```
315
+
316
+ **Execute with return to monitor:**
317
+ ```
318
+ Run $0800 and return to monitor
319
+ ```
320
+
321
+ **Set PC without executing:**
322
+ ```
323
+ Set PC to $0800 but don't execute yet
324
+ ```
325
+
326
+ ### Memory Operations
327
+
328
+ **Load binary file to memory:**
329
+ ```
330
+ Load the file ~/program.bin into memory at address $2000
331
+ ```
332
+
333
+ **Save memory range to file:**
334
+ ```
335
+ Save 256 bytes from memory address $0800 to ~/output.bin
336
+ ```
337
+
338
+ **Save memory region:**
339
+ ```
340
+ Read 1024 bytes starting at $4000 and save them to ~/dump.bin
341
+ ```
342
+
343
+ ### Emulator Control
344
+
345
+ **Power on:**
346
+ ```
347
+ Turn on the emulator
348
+ ```
349
+
350
+ **Power off:**
351
+ ```
352
+ Turn off the emulator
353
+ ```
354
+
355
+ **Reboot (cold reset):**
356
+ ```
357
+ Reboot the emulator
358
+ ```
359
+
360
+ **Warm reset:**
361
+ ```
362
+ Send Ctrl+Reset to the emulator
363
+ ```
364
+
365
+ **Break program:**
366
+ ```
367
+ Send Ctrl+C to the emulator
368
+ ```
369
+
370
+ ---
371
+
372
+ See also: [[Debugger]], [[Disk-Drives]], [[Getting-Started]]
@@ -0,0 +1,303 @@
1
+ # Architecture Overview
2
+
3
+ This page describes the internal architecture of the Apple //e emulator, covering the two-layer design, audio-driven timing model, WebAssembly interface, and build system.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Two-Layer Design](#two-layer-design)
10
+ - [Component Wiring](#component-wiring)
11
+ - [Audio-Driven Timing](#audio-driven-timing)
12
+ - [Frame Synchronization](#frame-synchronization)
13
+ - [WASM Interface](#wasm-interface)
14
+ - [Rendering Pipeline](#rendering-pipeline)
15
+ - [Build System](#build-system)
16
+ - [Key Constants](#key-constants)
17
+
18
+ ---
19
+
20
+ ## Two-Layer Design
21
+
22
+ The emulator is split into two distinct layers:
23
+
24
+ **C++ Core** (`src/core/`) -- Pure emulation logic compiled to WebAssembly. This layer has no browser dependencies and contains:
25
+
26
+ | Component | File | Responsibility |
27
+ |-----------|------|----------------|
28
+ | CPU | `cpu/cpu6502.cpp` | Cycle-accurate 65C02 processor |
29
+ | MMU | `mmu/mmu.cpp` | 128KB memory, soft switches, expansion slots |
30
+ | Video | `video/video.cpp` | Per-scanline rendering of all 6 video modes |
31
+ | Audio | `audio/audio.cpp` | Speaker toggle tracking and sample generation |
32
+ | Disk | `disk-image/` | DSK/DO/PO/NIB/WOZ format support with GCR encoding |
33
+ | Input | `input/keyboard.cpp` | Browser keycode to Apple II keycode translation |
34
+ | Cards | `cards/` | Pluggable expansion card system (Disk II, Mockingboard, Thunderclock, Mouse) |
35
+ | Emulator | `emulator.cpp` | Core coordinator, state serialization |
36
+
37
+ **JavaScript Layer** (`src/js/`) -- Browser integration using vanilla ES6 modules (no frameworks):
38
+
39
+ | Component | Directory | Responsibility |
40
+ |-----------|-----------|----------------|
41
+ | Main | `main.js` | `AppleIIeEmulator` class, initialization, render loop |
42
+ | Audio | `audio/` | Web Audio API driver, AudioWorklet processor |
43
+ | Display | `display/` | WebGL renderer, CRT shader effects |
44
+ | Disk Manager | `disk-manager/` | Disk drive UI, persistence, surface rendering |
45
+ | File Explorer | `file-explorer/` | DOS 3.3 and ProDOS disk browser |
46
+ | Debug | `debug/` | CPU debugger, memory browser, soft switch monitor, etc. |
47
+ | State | `state/` | Save state manager (autosave + 5 manual slots) |
48
+ | Input | `input/` | Keyboard handling, text selection, joystick, mouse |
49
+ | Windows | `windows/` | Base window class and window manager |
50
+
51
+ The C++ core is compiled to WebAssembly using Emscripten and exposed to JavaScript through a flat C function interface defined in `src/bindings/wasm_interface.cpp`.
52
+
53
+ ---
54
+
55
+ ## Component Wiring
56
+
57
+ The `Emulator` class (in `emulator.cpp`) acts as the central coordinator. During construction, it creates all subsystems and wires them together using callbacks:
58
+
59
+ ```
60
+ Emulator
61
+ |
62
+ +-- CPU6502 (read/write callbacks -> MMU)
63
+ |
64
+ +-- MMU (keyboard, speaker, button, cycle callbacks -> Emulator)
65
+ | |
66
+ | +-- ExpansionCard slots[1..7]
67
+ | +-- Slot 3: 80-column (built-in)
68
+ | +-- Slot 4: MockingboardCard
69
+ | +-- Slot 5: ThunderclockCard
70
+ | +-- Slot 6: Disk2Card
71
+ |
72
+ +-- Video (cycle callback -> CPU, switch callback -> MMU)
73
+ |
74
+ +-- Audio (Mockingboard pointer for stereo mixing)
75
+ |
76
+ +-- Keyboard (key callback -> Emulator)
77
+ ```
78
+
79
+ The CPU does not access memory directly. Instead, it calls lambda functions provided at construction time that route through the MMU:
80
+
81
+ ```cpp
82
+ cpu_ = std::make_unique<CPU6502>(
83
+ [this](uint16_t addr) { return cpuRead(addr); },
84
+ [this](uint16_t addr, uint8_t val) { cpuWrite(addr, val); },
85
+ CPUVariant::CMOS_65C02);
86
+ ```
87
+
88
+ The MMU in turn delegates I/O space reads/writes to expansion cards based on the address range, and invokes speaker, keyboard, and button callbacks to communicate back to the Emulator.
89
+
90
+ ---
91
+
92
+ ## Audio-Driven Timing
93
+
94
+ The emulator uses the Web Audio API as its primary timing source rather than `requestAnimationFrame` or `setInterval`. This approach provides several advantages:
95
+
96
+ 1. **Precise timing** -- The audio callback runs at exactly 48,000 Hz, providing consistent sample-level timing.
97
+ 2. **Background tab support** -- Web Audio continues to fire in background tabs, keeping emulation running when the tab is not visible.
98
+ 3. **Synchronized audio** -- Speaker clicks and Mockingboard output are generated in lockstep with CPU execution, preventing drift.
99
+
100
+ ### How It Works
101
+
102
+ The timing chain flows as follows:
103
+
104
+ ```
105
+ AudioWorklet (48kHz)
106
+ --> requests 1600 sample frames from main thread
107
+ --> AudioDriver.generateSamples(count)
108
+ --> WASM _generateStereoAudioSamples(buffer, count)
109
+ --> Emulator::runCycles(count * CYCLES_PER_SAMPLE * speedMultiplier)
110
+ --> CPU executes instructions
111
+ --> Video renders scanlines progressively
112
+ --> Disk controller updates per instruction
113
+ --> Mockingboard timers tick
114
+ --> Audio::generateStereoSamples() produces speaker + Mockingboard output
115
+ --> JS copies samples from WASM heap
116
+ --> Samples sent back to AudioWorklet via postMessage
117
+ --> AudioWorklet deinterleaves into L/R channels for output
118
+ ```
119
+
120
+ The AudioWorklet processor (`audio-worklet.js`) runs on a separate thread. It processes 128 samples at a time in its `process()` method, consuming from an internal buffer. When the buffer drops below 1,600 frames, it sends a `requestSamples` message to the main thread. The main thread responds by running the emulator for the required number of cycles and returning the generated samples.
121
+
122
+ Each audio sample requires approximately 21.3 CPU cycles (`1,023,000 / 48,000`). The WASM function `generateStereoAudioSamples` runs the emulator for `sampleCount * CYCLES_PER_SAMPLE * speedMultiplier` cycles, then generates interleaved stereo samples (speaker centered on both channels, Mockingboard PSG1 on left, PSG2 on right).
123
+
124
+ ### Fallback Timing
125
+
126
+ When Web Audio is unavailable (suspended by autoplay policy, no audio hardware), the `AudioDriver` falls back to a `setInterval` at 60 Hz, running approximately 17,050 cycles per tick. Audio resumes automatically on the first user interaction.
127
+
128
+ ---
129
+
130
+ ## Frame Synchronization
131
+
132
+ Frame boundaries are detected inside the emulator's main execution loop. After each instruction, the emulator checks whether `CYCLES_PER_FRAME` (17,030) cycles have elapsed since the last frame:
133
+
134
+ ```cpp
135
+ if (currentCycle - lastFrameCycle_ >= CYCLES_PER_FRAME) {
136
+ lastFrameCycle_ += CYCLES_PER_FRAME; // Aligned increment, no drift
137
+ video_->renderFrame();
138
+ video_->beginNewFrame(lastFrameCycle_);
139
+ frameReady_ = true;
140
+ }
141
+ ```
142
+
143
+ The frame boundary is advanced by exactly `CYCLES_PER_FRAME` rather than set to the current cycle count. This prevents drift and keeps the VBL detection at `$C019` synchronized with raster effects.
144
+
145
+ On the JavaScript side, `consumeFrameSamples()` tracks how many audio samples have been generated. At 48,000 Hz / 60 Hz = 800 samples per frame, this provides frame-level synchronization. When one or more frames' worth of samples are generated, `AudioDriver` triggers `onFrameReady`, which calls `renderFrame()` to upload the framebuffer to the WebGL texture.
146
+
147
+ The `requestAnimationFrame` render loop handles display updates for the non-audio path: debug window updates, beam crosshair overlays, drive LED animations, and forced re-renders when the CPU is paused.
148
+
149
+ ---
150
+
151
+ ## WASM Interface
152
+
153
+ The C++ core is exposed to JavaScript through a single global `Emulator` instance accessed via flat C functions in `wasm_interface.cpp`. A single static pointer `g_emulator` holds the instance:
154
+
155
+ ```cpp
156
+ static a2e::Emulator *g_emulator = nullptr;
157
+ ```
158
+
159
+ ### Memory Management
160
+
161
+ JavaScript uses Emscripten's `_malloc` / `_free` for WASM heap allocation and `HEAPU8` / `HEAPF32` for direct memory access. String conversion uses `stringToUTF8()` and `UTF8ToString()`. Example pattern for audio:
162
+
163
+ ```javascript
164
+ const bufferPtr = wasmModule._malloc(count * 2 * 4); // stereo floats
165
+ wasmModule._generateStereoAudioSamples(bufferPtr, count);
166
+ for (let i = 0; i < count * 2; i++) {
167
+ samples[i] = wasmModule.HEAPF32[(bufferPtr >> 2) + i];
168
+ }
169
+ wasmModule._free(bufferPtr);
170
+ ```
171
+
172
+ ### Exported Functions
173
+
174
+ All WASM exports are listed explicitly in `CMakeLists.txt` under `EXPORTED_FUNCTIONS`. To add a new export:
175
+
176
+ 1. Define the `extern "C"` function in `wasm_interface.cpp` with `EMSCRIPTEN_KEEPALIVE`
177
+ 2. Add the mangled name (prefixed with `_`) to the `EXPORTED_FUNCTIONS` list in `CMakeLists.txt`
178
+ 3. Rebuild WASM with `npm run build:wasm`
179
+
180
+ The exported API covers:
181
+
182
+ | Category | Examples |
183
+ |----------|----------|
184
+ | Lifecycle | `_init`, `_reset`, `_warmReset` |
185
+ | Execution | `_runCycles`, `_generateStereoAudioSamples`, `_stepInstruction` |
186
+ | CPU State | `_getPC`, `_getA`, `_getX`, `_getY`, `_getSP`, `_getP`, `_getTotalCycles` |
187
+ | Memory | `_readMemory`, `_writeMemory`, `_peekMemory`, `_readMainRAM` |
188
+ | Video | `_getFramebuffer`, `_getFramebufferSize`, `_isFrameReady`, `_forceRenderFrame` |
189
+ | Audio | `_setAudioVolume`, `_setAudioMuted` |
190
+ | Disk | `_insertDisk`, `_ejectDisk`, `_getDiskData`, `_isDiskInserted` |
191
+ | Debug | `_addBreakpoint`, `_stepOver`, `_stepOut`, `_addWatchpoint`, `_setTraceEnabled` |
192
+ | Expansion | `_getSlotCard`, `_setSlotCard`, `_isSlotEmpty` |
193
+ | Filesystem | `_isDOS33Format`, `_isProDOSFormat`, `_getDOS33Catalog`, `_getProDOSCatalog` |
194
+
195
+ ---
196
+
197
+ ## Rendering Pipeline
198
+
199
+ The rendering pipeline has two stages:
200
+
201
+ **C++ Video Rendering** -- The `Video` class renders into a 560x384 RGBA framebuffer (280x192 doubled). Rendering is progressive: `renderUpToCycle()` is called after each CPU instruction to render scanlines up to the current beam position. At frame boundaries, `renderFrame()` finalizes the current frame using a change log that records video switch changes at specific cycles.
202
+
203
+ **WebGL Display** -- The JavaScript `WebGLRenderer` uploads the framebuffer as a texture and applies CRT shader effects (scanlines, curvature, bloom, phosphor glow). The display pipeline:
204
+
205
+ ```
206
+ WASM Framebuffer (560x384 RGBA)
207
+ --> JS reads via HEAPU8[fbPtr..fbPtr+fbSize]
208
+ --> WebGLRenderer.updateTexture(framebuffer)
209
+ --> Fragment shader applies CRT effects
210
+ --> Canvas displays final output
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Build System
216
+
217
+ The project uses CMake for C++ compilation and Vite for JavaScript bundling.
218
+
219
+ ### WASM Build
220
+
221
+ ```bash
222
+ npm run build:wasm
223
+ ```
224
+
225
+ This invokes Emscripten's `emcmake cmake` and `emmake make` to compile the C++ core to WebAssembly. Key Emscripten settings:
226
+
227
+ | Setting | Value | Purpose |
228
+ |---------|-------|---------|
229
+ | `WASM` | 1 | Output WebAssembly |
230
+ | `MODULARIZE` | 1 | Wrap in factory function |
231
+ | `EXPORT_NAME` | `createA2EModule` | Global factory name |
232
+ | `ALLOW_MEMORY_GROWTH` | 1 | Dynamic heap expansion |
233
+ | `INITIAL_MEMORY` | 32 MB | Starting heap size |
234
+ | `MAXIMUM_MEMORY` | 64 MB | Maximum heap size |
235
+ | `NO_EXIT_RUNTIME` | 1 | Keep runtime alive |
236
+ | `ASYNCIFY` | 0 | Disabled (not needed) |
237
+ | Optimization | `-O3 -flto` | Full optimization with LTO |
238
+
239
+ Output files (`a2e.js` and `a2e.wasm`) are copied to `public/` after compilation.
240
+
241
+ ### ROM Embedding
242
+
243
+ ROM files are embedded at compile time. A shell script (`scripts/generate_roms.sh`) converts binary ROM files into C arrays in `generated/roms.cpp`, which is `#include`-ed by `emulator.cpp`. The embedded ROMs are:
244
+
245
+ | ROM File | Size | Purpose |
246
+ |----------|------|---------|
247
+ | `342-0349-B-C0-FF.bin` | 16 KB | System ROM ($C000-$FFFF) |
248
+ | `342-0273-A-US-UK.bin` | 4 KB | Character ROM (US/UK) |
249
+ | `341-0027.bin` | 256 bytes | Disk II controller ROM |
250
+ | `Thunderclock Plus ROM.bin` | 2 KB | Thunderclock card ROM |
251
+ | `Apple Mouse Interface Card ROM` | 2 KB | Mouse card ROM |
252
+
253
+ ### JavaScript Build
254
+
255
+ ```bash
256
+ npm run dev # Vite dev server at localhost:3000 with hot reload
257
+ npm run build # Production build (WASM + Vite bundle) to dist/
258
+ ```
259
+
260
+ The Vite build handles ES6 module bundling, CSS processing, and asset optimization. The audio worklet (`audio-worklet.js`) is loaded separately since worklets cannot be bundled.
261
+
262
+ ### Native Build (Testing)
263
+
264
+ ```bash
265
+ mkdir -p build-native && cd build-native
266
+ cmake ..
267
+ make -j$(sysctl -n hw.ncpu)
268
+ ctest --verbose
269
+ ```
270
+
271
+ The native build compiles test executables for CPU compliance (Klaus Dormann), Thunderclock card behavior, and GCR encoding. The emulator itself does not have a native runtime target.
272
+
273
+ ---
274
+
275
+ ## Key Constants
276
+
277
+ Defined in `src/core/types.hpp`:
278
+
279
+ | Constant | Value | Description |
280
+ |----------|-------|-------------|
281
+ | `CPU_CLOCK_HZ` | 1,023,000 | CPU clock frequency (1.023 MHz) |
282
+ | `AUDIO_SAMPLE_RATE` | 48,000 | Audio output sample rate |
283
+ | `CYCLES_PER_SAMPLE` | ~21.3125 | CPU cycles per audio sample |
284
+ | `CYCLES_PER_SCANLINE` | 65 | CPU cycles per horizontal scanline |
285
+ | `SCANLINES_PER_FRAME` | 262 | Total scanlines per frame (192 visible + 70 VBL) |
286
+ | `CYCLES_PER_FRAME` | 17,030 | CPU cycles per video frame (65 x 262) |
287
+ | `SCREEN_WIDTH` | 560 | Framebuffer width (280 x 2) |
288
+ | `SCREEN_HEIGHT` | 384 | Framebuffer height (192 x 2) |
289
+ | `FRAMEBUFFER_SIZE` | 860,160 | Framebuffer byte size (560 x 384 x 4 RGBA) |
290
+ | `MAIN_RAM_SIZE` | 65,536 | Main RAM (64 KB) |
291
+ | `AUX_RAM_SIZE` | 65,536 | Auxiliary RAM (64 KB) |
292
+ | `ROM_SIZE` | 16,384 | System ROM (16 KB) |
293
+
294
+ ---
295
+
296
+ ## See Also
297
+
298
+ - [[CPU-Emulation]] -- 65C02 processor details
299
+ - [[Memory-System]] -- MMU, bank switching, soft switches
300
+ - [[Video-Rendering]] -- Per-scanline rendering and video modes
301
+ - [[Audio-System]] -- Speaker and Mockingboard audio
302
+ - [[Expansion-Slots]] -- Card architecture and slot memory map
303
+ - [[Save-States]] -- Binary state serialization format