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,458 @@
1
+ /*
2
+ * soft-switch-window.js - Soft switch monitor window displaying switch states and addresses
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ import { BaseWindow } from "../windows/base-window.js";
9
+
10
+ export class SoftSwitchWindow extends BaseWindow {
11
+ constructor(wasmModule) {
12
+ super({
13
+ id: "soft-switches",
14
+ title: "Soft Switches",
15
+ minWidth: 325,
16
+ minHeight: 200,
17
+ maxWidth: 325,
18
+ maxHeight: Infinity,
19
+ defaultWidth: 325,
20
+ defaultHeight: 500,
21
+ });
22
+
23
+ this.wasmModule = wasmModule;
24
+
25
+ // Define all soft switches with their bit positions, addresses, and descriptions
26
+ // Bit positions match the 64-bit state returned by getSoftSwitchState/getSoftSwitchStateHigh
27
+ this.switchGroups = [
28
+ {
29
+ title: "Display Mode",
30
+ switches: [
31
+ {
32
+ id: "text",
33
+ bit: 0,
34
+ name: "TEXT",
35
+ addr: "$C050/51",
36
+ desc: "Text mode",
37
+ },
38
+ {
39
+ id: "mixed",
40
+ bit: 1,
41
+ name: "MIXED",
42
+ addr: "$C052/53",
43
+ desc: "Mixed text+graphics",
44
+ },
45
+ {
46
+ id: "page2",
47
+ bit: 2,
48
+ name: "PAGE2",
49
+ addr: "$C054/55",
50
+ desc: "Display page 2",
51
+ },
52
+ {
53
+ id: "hires",
54
+ bit: 3,
55
+ name: "HIRES",
56
+ addr: "$C056/57",
57
+ desc: "Hi-res graphics",
58
+ },
59
+ {
60
+ id: "col80",
61
+ bit: 4,
62
+ name: "80COL",
63
+ addr: "$C00C/0D",
64
+ desc: "80 column mode",
65
+ },
66
+ {
67
+ id: "altchar",
68
+ bit: 5,
69
+ name: "ALTCHAR",
70
+ addr: "$C00E/0F",
71
+ desc: "Alt charset (MouseText)",
72
+ },
73
+ {
74
+ id: "dhires",
75
+ bit: 28,
76
+ name: "DHIRES",
77
+ addr: "computed",
78
+ desc: "Double hi-res active",
79
+ },
80
+ ],
81
+ },
82
+ {
83
+ title: "Memory Banking",
84
+ switches: [
85
+ {
86
+ id: "store80",
87
+ bit: 6,
88
+ name: "80STORE",
89
+ addr: "$C000/01",
90
+ desc: "PAGE2 selects aux mem",
91
+ },
92
+ {
93
+ id: "ramrd",
94
+ bit: 7,
95
+ name: "RAMRD",
96
+ addr: "$C002/03",
97
+ desc: "Read from aux RAM",
98
+ },
99
+ {
100
+ id: "ramwrt",
101
+ bit: 8,
102
+ name: "RAMWRT",
103
+ addr: "$C004/05",
104
+ desc: "Write to aux RAM",
105
+ },
106
+ {
107
+ id: "intcxrom",
108
+ bit: 9,
109
+ name: "INTCXROM",
110
+ addr: "$C006/07",
111
+ desc: "Internal $Cxxx ROM",
112
+ },
113
+ {
114
+ id: "altzp",
115
+ bit: 10,
116
+ name: "ALTZP",
117
+ addr: "$C008/09",
118
+ desc: "Aux zero page/stack",
119
+ },
120
+ {
121
+ id: "slotc3rom",
122
+ bit: 11,
123
+ name: "SLOTC3ROM",
124
+ addr: "$C00A/0B",
125
+ desc: "Slot 3 ROM enabled",
126
+ },
127
+ {
128
+ id: "intc8rom",
129
+ bit: 12,
130
+ name: "INTC8ROM",
131
+ addr: "internal",
132
+ desc: "Internal $C800 ROM",
133
+ },
134
+ ],
135
+ },
136
+ {
137
+ title: "Language Card",
138
+ switches: [
139
+ {
140
+ id: "lcram",
141
+ bit: 13,
142
+ name: "LCRAM",
143
+ addr: "$C080-8F",
144
+ desc: "LC RAM read enabled",
145
+ },
146
+ {
147
+ id: "lcbank2",
148
+ bit: 14,
149
+ name: "LCBANK2",
150
+ addr: "$C080-8F",
151
+ desc: "LC bank 2 selected",
152
+ },
153
+ {
154
+ id: "lcwrite",
155
+ bit: 15,
156
+ name: "LCWRITE",
157
+ addr: "$C080-8F",
158
+ desc: "LC RAM write enabled",
159
+ },
160
+ {
161
+ id: "lcprewrite",
162
+ bit: 16,
163
+ name: "LCPREWRT",
164
+ addr: "$C080-8F",
165
+ desc: "LC pre-write state",
166
+ },
167
+ ],
168
+ },
169
+ {
170
+ title: "Annunciators",
171
+ switches: [
172
+ {
173
+ id: "an0",
174
+ bit: 17,
175
+ name: "AN0",
176
+ addr: "$C058/59",
177
+ desc: "Annunciator 0",
178
+ },
179
+ {
180
+ id: "an1",
181
+ bit: 18,
182
+ name: "AN1",
183
+ addr: "$C05A/5B",
184
+ desc: "Annunciator 1",
185
+ },
186
+ {
187
+ id: "an2",
188
+ bit: 19,
189
+ name: "AN2",
190
+ addr: "$C05C/5D",
191
+ desc: "Annunciator 2",
192
+ },
193
+ {
194
+ id: "an3",
195
+ bit: 20,
196
+ name: "AN3",
197
+ addr: "$C05E/5F",
198
+ desc: "Annunciator 3 / DHIRES",
199
+ },
200
+ ],
201
+ },
202
+ {
203
+ title: "I/O Status",
204
+ switches: [
205
+ {
206
+ id: "vblbar",
207
+ bit: 21,
208
+ name: "VBLBAR",
209
+ addr: "$C019",
210
+ desc: "Vertical blank",
211
+ readOnly: true,
212
+ },
213
+ {
214
+ id: "cassout",
215
+ bit: 22,
216
+ name: "CASSOUT",
217
+ addr: "$C020",
218
+ desc: "Cassette output",
219
+ },
220
+ {
221
+ id: "cassin",
222
+ bit: 23,
223
+ name: "CASSIN",
224
+ addr: "$C060",
225
+ desc: "Cassette input",
226
+ readOnly: true,
227
+ },
228
+ ],
229
+ },
230
+ {
231
+ title: "Buttons",
232
+ switches: [
233
+ {
234
+ id: "btn0",
235
+ bit: 24,
236
+ name: "BTN0",
237
+ addr: "$C061",
238
+ desc: "Open Apple / Button 0",
239
+ readOnly: true,
240
+ },
241
+ {
242
+ id: "btn1",
243
+ bit: 25,
244
+ name: "BTN1",
245
+ addr: "$C062",
246
+ desc: "Closed Apple / Button 1",
247
+ readOnly: true,
248
+ },
249
+ {
250
+ id: "btn2",
251
+ bit: 26,
252
+ name: "BTN2",
253
+ addr: "$C063",
254
+ desc: "Button 2 / Shift",
255
+ readOnly: true,
256
+ },
257
+ ],
258
+ },
259
+ {
260
+ title: "Keyboard",
261
+ switches: [
262
+ {
263
+ id: "keyavail",
264
+ bit: 27,
265
+ name: "KEYAVAIL",
266
+ addr: "$C000",
267
+ desc: "Key available (bit 7)",
268
+ readOnly: true,
269
+ },
270
+ ],
271
+ },
272
+ {
273
+ title: "Other",
274
+ switches: [
275
+ {
276
+ id: "ioudis",
277
+ bit: 29,
278
+ name: "IOUDIS",
279
+ addr: "$C07E/7F",
280
+ desc: "IOU disable (IIc)",
281
+ },
282
+ ],
283
+ },
284
+ ];
285
+
286
+ // Reference addresses (read-only status registers)
287
+ this.statusRegisters = [
288
+ { addr: "$C011", name: "RDLCBNK2", desc: "LC bank 2 selected" },
289
+ { addr: "$C012", name: "RDLCRAM", desc: "LC RAM read enabled" },
290
+ { addr: "$C013", name: "RDRAMRD", desc: "Aux RAM read" },
291
+ { addr: "$C014", name: "RDRAMWRT", desc: "Aux RAM write" },
292
+ { addr: "$C015", name: "RDCXROM", desc: "Internal $Cxxx ROM" },
293
+ { addr: "$C016", name: "RDALTZP", desc: "Aux zero page" },
294
+ { addr: "$C017", name: "RDC3ROM", desc: "Slot 3 ROM" },
295
+ { addr: "$C018", name: "RD80STORE", desc: "80STORE enabled" },
296
+ { addr: "$C019", name: "RDVBLBAR", desc: "Vertical blank" },
297
+ { addr: "$C01A", name: "RDTEXT", desc: "Text mode" },
298
+ { addr: "$C01B", name: "RDMIXED", desc: "Mixed mode" },
299
+ { addr: "$C01C", name: "RDPAGE2", desc: "Page 2" },
300
+ { addr: "$C01D", name: "RDHIRES", desc: "Hi-res mode" },
301
+ { addr: "$C01E", name: "RDALTCHAR", desc: "Alt charset" },
302
+ { addr: "$C01F", name: "RD80COL", desc: "80 column mode" },
303
+ ];
304
+
305
+ // Other I/O addresses (for reference)
306
+ this.ioAddresses = [
307
+ { addr: "$C010", name: "KBDSTRB", desc: "Clear keyboard strobe" },
308
+ { addr: "$C030", name: "SPKR", desc: "Speaker toggle" },
309
+ { addr: "$C040", name: "STROBE", desc: "Utility strobe" },
310
+ { addr: "$C064", name: "PDL0", desc: "Paddle 0 (joystick X)" },
311
+ { addr: "$C065", name: "PDL1", desc: "Paddle 1 (joystick Y)" },
312
+ { addr: "$C066", name: "PDL2", desc: "Paddle 2" },
313
+ { addr: "$C067", name: "PDL3", desc: "Paddle 3" },
314
+ { addr: "$C070", name: "PTRIG", desc: "Paddle trigger" },
315
+ ];
316
+
317
+ // Slot I/O ranges
318
+ this.slotRanges = [
319
+ { range: "$C090-9F", slot: 1, desc: "Slot 1 I/O" },
320
+ { range: "$C0A0-AF", slot: 2, desc: "Slot 2 I/O" },
321
+ { range: "$C0B0-BF", slot: 3, desc: "Slot 3 I/O" },
322
+ { range: "$C0C0-CF", slot: 4, desc: "Slot 4 I/O" },
323
+ { range: "$C0D0-DF", slot: 5, desc: "Slot 5 I/O" },
324
+ { range: "$C0E0-EF", slot: 6, desc: "Slot 6 I/O (Disk II)" },
325
+ { range: "$C0F0-FF", slot: 7, desc: "Slot 7 I/O" },
326
+ ];
327
+ }
328
+
329
+ renderContent() {
330
+ let html = '<div class="softswitch-content">';
331
+
332
+ // Render switch groups
333
+ for (const group of this.switchGroups) {
334
+ html += `
335
+ <div class="switch-group">
336
+ <div class="switch-group-title">${group.title}</div>
337
+ <div class="switch-list">
338
+ `;
339
+
340
+ for (const sw of group.switches) {
341
+ const readOnlyClass = sw.readOnly ? " read-only" : "";
342
+ html += `
343
+ <div class="switch-item${readOnlyClass}" id="sw-item-${sw.id}">
344
+ <span class="switch-addr">${sw.addr}</span>
345
+ <span class="switch-badge" id="sw-${sw.id}">${sw.name}</span>
346
+ <span class="switch-desc">${sw.desc}</span>
347
+ </div>
348
+ `;
349
+ }
350
+
351
+ html += `
352
+ </div>
353
+ </div>
354
+ `;
355
+ }
356
+
357
+ // Add collapsible reference section
358
+ html += `
359
+ <div class="switch-group reference-section">
360
+ <div class="switch-group-title collapsible" id="ref-toggle">
361
+ ▶ I/O Reference
362
+ </div>
363
+ <div class="switch-list reference-list hidden" id="ref-content">
364
+ `;
365
+
366
+ // Status registers
367
+ html += '<div class="ref-subtitle">Status Registers ($C011-$C01F)</div>';
368
+ for (const reg of this.statusRegisters) {
369
+ html += `
370
+ <div class="ref-item">
371
+ <span class="ref-addr">${reg.addr}</span>
372
+ <span class="ref-name">${reg.name}</span>
373
+ <span class="ref-desc">${reg.desc}</span>
374
+ </div>
375
+ `;
376
+ }
377
+
378
+ // Other I/O
379
+ html += '<div class="ref-subtitle">Other I/O</div>';
380
+ for (const io of this.ioAddresses) {
381
+ html += `
382
+ <div class="ref-item">
383
+ <span class="ref-addr">${io.addr}</span>
384
+ <span class="ref-name">${io.name}</span>
385
+ <span class="ref-desc">${io.desc}</span>
386
+ </div>
387
+ `;
388
+ }
389
+
390
+ // Slot I/O
391
+ html += '<div class="ref-subtitle">Slot I/O</div>';
392
+ for (const slot of this.slotRanges) {
393
+ html += `
394
+ <div class="ref-item">
395
+ <span class="ref-addr">${slot.range}</span>
396
+ <span class="ref-name">Slot ${slot.slot}</span>
397
+ <span class="ref-desc">${slot.desc}</span>
398
+ </div>
399
+ `;
400
+ }
401
+
402
+ html += `
403
+ </div>
404
+ </div>
405
+ `;
406
+
407
+ html += "</div>";
408
+ return html;
409
+ }
410
+
411
+ /**
412
+ * Called after content is rendered
413
+ */
414
+ onContentRendered() {
415
+ // Set up collapsible reference section
416
+ const toggle = this.contentElement.querySelector("#ref-toggle");
417
+ const content = this.contentElement.querySelector("#ref-content");
418
+
419
+ if (toggle && content) {
420
+ toggle.addEventListener("click", () => {
421
+ content.classList.toggle("hidden");
422
+ toggle.textContent = content.classList.contains("hidden")
423
+ ? "▶ I/O Reference"
424
+ : "▼ I/O Reference";
425
+ });
426
+ }
427
+
428
+ }
429
+
430
+ /**
431
+ * Update all soft switch states
432
+ */
433
+ update(wasmModule) {
434
+ this.wasmModule = wasmModule;
435
+
436
+ // Get both low and high 32-bit parts of the state
437
+ const stateLow = wasmModule._getSoftSwitchState();
438
+ const stateHigh = wasmModule._getSoftSwitchStateHigh
439
+ ? wasmModule._getSoftSwitchStateHigh()
440
+ : 0;
441
+
442
+ for (const group of this.switchGroups) {
443
+ for (const sw of group.switches) {
444
+ let isOn;
445
+ if (sw.bit < 32) {
446
+ isOn = (stateLow & (1 << sw.bit)) !== 0;
447
+ } else {
448
+ isOn = (stateHigh & (1 << (sw.bit - 32))) !== 0;
449
+ }
450
+
451
+ const badge = this.contentElement.querySelector(`#sw-${sw.id}`);
452
+ if (badge) {
453
+ badge.classList.toggle("active", isOn);
454
+ }
455
+ }
456
+ }
457
+ }
458
+ }
@@ -0,0 +1,221 @@
1
+ /*
2
+ * stack-viewer-window.js - Stack viewer debug window with live stack contents
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ import { BaseWindow } from "../windows/base-window.js";
9
+
10
+ export class StackViewerWindow extends BaseWindow {
11
+ constructor(wasmModule) {
12
+ super({
13
+ id: "stack-viewer",
14
+ title: "Stack Viewer",
15
+ defaultWidth: 280,
16
+ defaultHeight: 400,
17
+ minWidth: 280,
18
+ minHeight: 250,
19
+ maxWidth: 280,
20
+ });
21
+ this.wasmModule = wasmModule;
22
+ this.previousSP = 0xff;
23
+ this.returnAddresses = new Set(); // Track likely return addresses
24
+ }
25
+
26
+ renderContent() {
27
+ return `
28
+ <div class="stack-info">
29
+ <span class="stack-sp-label">SP:</span>
30
+ <span class="stack-sp-value">$FF</span>
31
+ <span class="stack-depth-label">Depth:</span>
32
+ <span class="stack-depth-value">0</span>
33
+ </div>
34
+ <div class="stack-depth-bar">
35
+ <div class="stack-depth-fill"></div>
36
+ </div>
37
+ <div class="stack-call-stack" id="call-stack"></div>
38
+ <div class="stack-header">
39
+ <span class="stack-col-addr">Addr</span>
40
+ <span class="stack-col-value">Value</span>
41
+ <span class="stack-col-info">Info</span>
42
+ </div>
43
+ <div class="stack-content"></div>
44
+ `;
45
+ }
46
+
47
+ onContentRendered() {
48
+ this.spValueSpan = this.contentElement.querySelector(".stack-sp-value");
49
+ this.depthValueSpan =
50
+ this.contentElement.querySelector(".stack-depth-value");
51
+ this.depthFill = this.contentElement.querySelector(".stack-depth-fill");
52
+ this.contentDiv = this.contentElement.querySelector(".stack-content");
53
+ }
54
+
55
+ analyzeReturnAddress(lowByte, highByte) {
56
+ // Return addresses on 6502 are pushed as addr-1 (JSR pushes PC+2, which points to last byte of JSR)
57
+ const retAddr = ((highByte << 8) | lowByte) + 1;
58
+
59
+ if (retAddr > 0xffff) return null;
60
+
61
+ // Try to disassemble the instruction at the return address
62
+ const disasm = this.wasmModule._disassembleAt(retAddr);
63
+ if (disasm) {
64
+ const disasmStr = this.wasmModule.UTF8ToString(disasm);
65
+ // Extract just the instruction mnemonic
66
+ const match = disasmStr.match(/:\s*[0-9A-F ]+\s+(\w+)/);
67
+ if (match) {
68
+ return {
69
+ addr: retAddr,
70
+ instr: match[1],
71
+ };
72
+ }
73
+ }
74
+ return { addr: retAddr, instr: "???" };
75
+ }
76
+
77
+ isLikelyReturnAddress(sp, wasmModule) {
78
+ if (sp >= 0xfe) return false; // Need at least 2 bytes
79
+
80
+ const low = wasmModule._peekMemory(0x100 + sp + 1);
81
+ const high = wasmModule._peekMemory(0x100 + sp + 2);
82
+ const addr = ((high << 8) | low) + 1;
83
+
84
+ return wasmModule._isLikelyReturnAddress(addr & 0xffff);
85
+ }
86
+
87
+ update(wasmModule) {
88
+ if (!this.isVisible || !this.contentDiv) return;
89
+
90
+ const sp = wasmModule._getSP();
91
+ const stackDepth = 0xff - sp;
92
+
93
+ // Update SP display
94
+ this.spValueSpan.textContent = `$${this.formatHex(sp, 2)}`;
95
+ this.depthValueSpan.textContent = stackDepth.toString();
96
+
97
+ // Update depth bar (max depth is 256 bytes)
98
+ const depthPercent = (stackDepth / 256) * 100;
99
+ this.depthFill.style.width = `${depthPercent}%`;
100
+
101
+ // Color code depth bar
102
+ if (depthPercent > 80) {
103
+ this.depthFill.classList.add("danger");
104
+ this.depthFill.classList.remove("warning");
105
+ } else if (depthPercent > 60) {
106
+ this.depthFill.classList.add("warning");
107
+ this.depthFill.classList.remove("danger");
108
+ } else {
109
+ this.depthFill.classList.remove("warning", "danger");
110
+ }
111
+
112
+ // Build stack view (from top of stack down to SP)
113
+ let html = "";
114
+ let i = 0xff;
115
+ let skipReturnAddr = false;
116
+
117
+ while (i > sp) {
118
+ const addr = 0x100 + i;
119
+ const value = wasmModule._peekMemory(addr);
120
+ const isSP = i === sp + 1; // Current top of stack
121
+
122
+ // Try to detect return addresses (pairs of bytes)
123
+ let returnInfo = null;
124
+ let isReturnAddr = false;
125
+
126
+ if (i > sp + 1) {
127
+ // Check if this could be the high byte of a return address
128
+ const prevValue = wasmModule._peekMemory(0x100 + i - 1);
129
+ const testAddr = ((value << 8) | prevValue) + 1;
130
+
131
+ // Heuristic: return addresses typically point to ROM or program area
132
+ if (
133
+ (testAddr >= 0x0800 && testAddr < 0xc000) ||
134
+ (testAddr >= 0xd000 && testAddr <= 0xffff)
135
+ ) {
136
+ // Check if the instruction before the return address was a JSR
137
+ const beforeRet = testAddr - 3;
138
+ if (beforeRet >= 0) {
139
+ const possibleJSR = wasmModule._peekMemory(beforeRet);
140
+ if (possibleJSR === 0x20) {
141
+ // JSR opcode
142
+ isReturnAddr = true;
143
+ returnInfo = this.analyzeReturnAddress(prevValue, value);
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ const classes = ["stack-entry"];
150
+ if (isSP) classes.push("stack-top");
151
+ if (skipReturnAddr) {
152
+ classes.push("return-addr-low");
153
+ skipReturnAddr = false;
154
+ } else if (isReturnAddr) {
155
+ classes.push("return-addr-high");
156
+ skipReturnAddr = true;
157
+ }
158
+
159
+ let infoStr = "";
160
+ if (isReturnAddr && returnInfo) {
161
+ infoStr = `→ $${this.formatHex(returnInfo.addr, 4)} (${returnInfo.instr})`;
162
+ } else if (value >= 0x20 && value < 0x7f) {
163
+ infoStr = `'${String.fromCharCode(value)}'`;
164
+ }
165
+
166
+ html += `
167
+ <div class="${classes.join(" ")}">
168
+ <span class="stack-addr">$${this.formatHex(addr, 4)}</span>
169
+ <span class="stack-value">$${this.formatHex(value, 2)}</span>
170
+ <span class="stack-info-text">${infoStr}</span>
171
+ </div>
172
+ `;
173
+
174
+ i--;
175
+ }
176
+
177
+ // Add empty stack marker if stack is empty
178
+ if (stackDepth === 0) {
179
+ html = '<div class="stack-empty">Stack is empty</div>';
180
+ }
181
+
182
+ this.contentDiv.innerHTML = html;
183
+ this.previousSP = sp;
184
+
185
+ // Build call stack summary
186
+ this.updateCallStack(wasmModule, sp);
187
+ }
188
+
189
+ /**
190
+ * Build a call stack summary by walking the stack for return addresses.
191
+ * Display: current_PC → caller → caller → ...
192
+ */
193
+ updateCallStack(wasmModule, sp) {
194
+ const callStackEl = this.contentElement.querySelector("#call-stack");
195
+ if (!callStackEl) return;
196
+
197
+ const pc = wasmModule._getPC();
198
+ const count = wasmModule._getCallStack();
199
+
200
+ if (count === 0) {
201
+ callStackEl.innerHTML = "";
202
+ return;
203
+ }
204
+
205
+ // Read packed CallStackEntry structs (4 bytes each: uint16_t returnAddr, uint16_t jsrTarget)
206
+ const bufPtr = wasmModule._getCallStackBuffer();
207
+ const heap = wasmModule.HEAPU8;
208
+
209
+ let stackHtml = '<span class="call-stack-label">Call:</span> ';
210
+ stackHtml += `<span class="call-stack-addr">$${this.formatHex(pc, 4)}</span>`;
211
+
212
+ for (let i = 0; i < count; i++) {
213
+ const offset = bufPtr + i * 4;
214
+ const retAddr = heap[offset] | (heap[offset + 1] << 8);
215
+ const jsrTarget = heap[offset + 2] | (heap[offset + 3] << 8);
216
+ stackHtml += ` ← <span class="call-stack-addr" title="Returns to $${this.formatHex(retAddr, 4)}">$${this.formatHex(jsrTarget, 4)}</span>`;
217
+ }
218
+
219
+ callStackEl.innerHTML = stackHtml;
220
+ }
221
+ }