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.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- package/wiki/Video-Rendering.md +658 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_gcr_encoding.cpp - Unit tests for GCR encoding routines
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#define CATCH_CONFIG_MAIN
|
|
6
|
+
#include "catch.hpp"
|
|
7
|
+
|
|
8
|
+
#include "gcr_encoding.hpp"
|
|
9
|
+
|
|
10
|
+
#include <set>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
|
|
13
|
+
using namespace a2e;
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// encode4and4 tests
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
TEST_CASE("encode4and4 splits byte into odd and even bit pairs", "[gcr][4and4]") {
|
|
20
|
+
SECTION("zero encodes to (0xAA, 0xAA)") {
|
|
21
|
+
auto [odd, even] = GCR::encode4and4(0x00);
|
|
22
|
+
CHECK(odd == 0xAA);
|
|
23
|
+
CHECK(even == 0xAA);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
SECTION("0xFF encodes correctly") {
|
|
27
|
+
auto [odd, even] = GCR::encode4and4(0xFF);
|
|
28
|
+
CHECK(odd == 0xFF);
|
|
29
|
+
CHECK(even == 0xFF);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
SECTION("both bytes always have high bit set") {
|
|
33
|
+
for (int v = 0; v < 256; v++) {
|
|
34
|
+
auto [odd, even] = GCR::encode4and4(static_cast<uint8_t>(v));
|
|
35
|
+
REQUIRE((odd & 0x80) != 0);
|
|
36
|
+
REQUIRE((even & 0x80) != 0);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
SECTION("decoding recovers original value") {
|
|
41
|
+
// 4-and-4 decoding: AND the two bytes and strip extra bits
|
|
42
|
+
for (int v = 0; v < 256; v++) {
|
|
43
|
+
auto [odd, even] = GCR::encode4and4(static_cast<uint8_t>(v));
|
|
44
|
+
// Odd byte has bits 7,5,3,1 set; even byte has bits 6,4,2,0.
|
|
45
|
+
// To decode: shift odd left 1 then AND, combined with even.
|
|
46
|
+
uint8_t decoded = ((odd << 1) | 0x01) & even;
|
|
47
|
+
CHECK(decoded == static_cast<uint8_t>(v));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
SECTION("specific known value 0xFE") {
|
|
52
|
+
auto [odd, even] = GCR::encode4and4(0xFE);
|
|
53
|
+
// 0xFE = 1111 1110
|
|
54
|
+
// odd bits (7,5,3,1) = 1,1,1,1 -> positions 6,4,2,0 = 0x55 | 0xAA = 0xFF
|
|
55
|
+
// even bits (6,4,2,0) = 1,1,1,0 -> positions 6,4,2,0 = 0x54 | 0xAA = 0xFE
|
|
56
|
+
CHECK(odd == 0xFF);
|
|
57
|
+
CHECK(even == 0xFE);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// encode6and2 tests
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
TEST_CASE("encode6and2 produces 343 nibbles from 256-byte sector", "[gcr][6and2]") {
|
|
66
|
+
uint8_t sector[256];
|
|
67
|
+
memset(sector, 0, sizeof(sector));
|
|
68
|
+
|
|
69
|
+
auto nibbles = GCR::encode6and2(sector);
|
|
70
|
+
REQUIRE(nibbles.size() == 343);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
TEST_CASE("encode6and2 all nibbles have bit 7 set", "[gcr][6and2]") {
|
|
74
|
+
// Fill sector with sequential pattern
|
|
75
|
+
uint8_t sector[256];
|
|
76
|
+
for (int i = 0; i < 256; i++) {
|
|
77
|
+
sector[i] = static_cast<uint8_t>(i);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
auto nibbles = GCR::encode6and2(sector);
|
|
81
|
+
REQUIRE(nibbles.size() == 343);
|
|
82
|
+
|
|
83
|
+
for (size_t i = 0; i < nibbles.size(); i++) {
|
|
84
|
+
INFO("Nibble at position " << i << " is 0x" << std::hex << (int)nibbles[i]);
|
|
85
|
+
REQUIRE((nibbles[i] & 0x80) != 0);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
TEST_CASE("encode6and2 produces valid encoded nibbles from lookup table", "[gcr][6and2]") {
|
|
90
|
+
// All output nibbles should be values found in the ENCODE_6_AND_2 table
|
|
91
|
+
uint8_t sector[256];
|
|
92
|
+
for (int i = 0; i < 256; i++) {
|
|
93
|
+
sector[i] = static_cast<uint8_t>(i);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
auto nibbles = GCR::encode6and2(sector);
|
|
97
|
+
|
|
98
|
+
std::set<uint8_t> validNibbles(GCR::ENCODE_6_AND_2.begin(), GCR::ENCODE_6_AND_2.end());
|
|
99
|
+
|
|
100
|
+
for (size_t i = 0; i < nibbles.size(); i++) {
|
|
101
|
+
INFO("Nibble at position " << i << " is 0x" << std::hex << (int)nibbles[i]);
|
|
102
|
+
REQUIRE(validNibbles.count(nibbles[i]) == 1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
TEST_CASE("encode6and2 different input produces different output", "[gcr][6and2]") {
|
|
107
|
+
uint8_t sector_a[256], sector_b[256];
|
|
108
|
+
memset(sector_a, 0x00, sizeof(sector_a));
|
|
109
|
+
memset(sector_b, 0xFF, sizeof(sector_b));
|
|
110
|
+
|
|
111
|
+
auto nibbles_a = GCR::encode6and2(sector_a);
|
|
112
|
+
auto nibbles_b = GCR::encode6and2(sector_b);
|
|
113
|
+
|
|
114
|
+
REQUIRE(nibbles_a.size() == 343);
|
|
115
|
+
REQUIRE(nibbles_b.size() == 343);
|
|
116
|
+
REQUIRE(nibbles_a != nibbles_b);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// buildSector tests
|
|
121
|
+
// ============================================================================
|
|
122
|
+
|
|
123
|
+
TEST_CASE("buildSector contains address prologue D5 AA 96", "[gcr][buildSector]") {
|
|
124
|
+
uint8_t sector[256];
|
|
125
|
+
memset(sector, 0, sizeof(sector));
|
|
126
|
+
|
|
127
|
+
auto stream = GCR::buildSector(254, 0, 0, sector);
|
|
128
|
+
|
|
129
|
+
bool found = false;
|
|
130
|
+
for (size_t i = 0; i + 2 < stream.size(); i++) {
|
|
131
|
+
if (stream[i] == 0xD5 && stream[i + 1] == 0xAA && stream[i + 2] == 0x96) {
|
|
132
|
+
found = true;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
REQUIRE(found);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
TEST_CASE("buildSector contains data prologue D5 AA AD", "[gcr][buildSector]") {
|
|
140
|
+
uint8_t sector[256];
|
|
141
|
+
memset(sector, 0, sizeof(sector));
|
|
142
|
+
|
|
143
|
+
auto stream = GCR::buildSector(254, 0, 0, sector);
|
|
144
|
+
|
|
145
|
+
bool found = false;
|
|
146
|
+
for (size_t i = 0; i + 2 < stream.size(); i++) {
|
|
147
|
+
if (stream[i] == 0xD5 && stream[i + 1] == 0xAA && stream[i + 2] == 0xAD) {
|
|
148
|
+
found = true;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
REQUIRE(found);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
TEST_CASE("buildSector address prologue appears before data prologue", "[gcr][buildSector]") {
|
|
156
|
+
uint8_t sector[256];
|
|
157
|
+
memset(sector, 0, sizeof(sector));
|
|
158
|
+
|
|
159
|
+
auto stream = GCR::buildSector(254, 10, 5, sector);
|
|
160
|
+
|
|
161
|
+
size_t addrPos = 0, dataPos = 0;
|
|
162
|
+
bool foundAddr = false, foundData = false;
|
|
163
|
+
|
|
164
|
+
for (size_t i = 0; i + 2 < stream.size(); i++) {
|
|
165
|
+
if (!foundAddr && stream[i] == 0xD5 && stream[i + 1] == 0xAA && stream[i + 2] == 0x96) {
|
|
166
|
+
addrPos = i;
|
|
167
|
+
foundAddr = true;
|
|
168
|
+
}
|
|
169
|
+
if (!foundData && stream[i] == 0xD5 && stream[i + 1] == 0xAA && stream[i + 2] == 0xAD) {
|
|
170
|
+
dataPos = i;
|
|
171
|
+
foundData = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
REQUIRE(foundAddr);
|
|
176
|
+
REQUIRE(foundData);
|
|
177
|
+
REQUIRE(addrPos < dataPos);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
TEST_CASE("buildSector begins with sync bytes", "[gcr][buildSector]") {
|
|
181
|
+
uint8_t sector[256];
|
|
182
|
+
memset(sector, 0, sizeof(sector));
|
|
183
|
+
|
|
184
|
+
auto stream = GCR::buildSector(254, 0, 0, sector);
|
|
185
|
+
REQUIRE(stream.size() > 3);
|
|
186
|
+
|
|
187
|
+
// First bytes should be sync bytes (0xFF)
|
|
188
|
+
CHECK(stream[0] == GCR::SYNC_BYTE);
|
|
189
|
+
CHECK(stream[1] == GCR::SYNC_BYTE);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// ENCODE_6_AND_2 table tests
|
|
194
|
+
// ============================================================================
|
|
195
|
+
|
|
196
|
+
TEST_CASE("ENCODE_6_AND_2 table has exactly 64 entries", "[gcr][table]") {
|
|
197
|
+
REQUIRE(GCR::ENCODE_6_AND_2.size() == 64);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
TEST_CASE("ENCODE_6_AND_2 all entries have bit 7 set", "[gcr][table]") {
|
|
201
|
+
for (size_t i = 0; i < GCR::ENCODE_6_AND_2.size(); i++) {
|
|
202
|
+
INFO("Entry " << i << " = 0x" << std::hex << (int)GCR::ENCODE_6_AND_2[i]);
|
|
203
|
+
REQUIRE((GCR::ENCODE_6_AND_2[i] & 0x80) != 0);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
TEST_CASE("ENCODE_6_AND_2 all entries are unique", "[gcr][table]") {
|
|
208
|
+
std::set<uint8_t> seen;
|
|
209
|
+
for (size_t i = 0; i < GCR::ENCODE_6_AND_2.size(); i++) {
|
|
210
|
+
INFO("Duplicate at index " << i << " value 0x" << std::hex << (int)GCR::ENCODE_6_AND_2[i]);
|
|
211
|
+
REQUIRE(seen.insert(GCR::ENCODE_6_AND_2[i]).second);
|
|
212
|
+
}
|
|
213
|
+
REQUIRE(seen.size() == 64);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// Sync byte constant
|
|
218
|
+
// ============================================================================
|
|
219
|
+
|
|
220
|
+
TEST_CASE("SYNC_BYTE is 0xFF", "[gcr][constants]") {
|
|
221
|
+
REQUIRE(GCR::SYNC_BYTE == 0xFF);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
TEST_CASE("Address and data prologue constants are correct", "[gcr][constants]") {
|
|
225
|
+
CHECK(GCR::ADDR_PROLOGUE[0] == 0xD5);
|
|
226
|
+
CHECK(GCR::ADDR_PROLOGUE[1] == 0xAA);
|
|
227
|
+
CHECK(GCR::ADDR_PROLOGUE[2] == 0x96);
|
|
228
|
+
|
|
229
|
+
CHECK(GCR::DATA_PROLOGUE[0] == 0xD5);
|
|
230
|
+
CHECK(GCR::DATA_PROLOGUE[1] == 0xAA);
|
|
231
|
+
CHECK(GCR::DATA_PROLOGUE[2] == 0xAD);
|
|
232
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_keyboard.cpp - Unit tests for Apple IIe keyboard input handling
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#define CATCH_CONFIG_MAIN
|
|
6
|
+
#include "catch.hpp"
|
|
7
|
+
|
|
8
|
+
#include "keyboard.hpp"
|
|
9
|
+
|
|
10
|
+
using namespace a2e;
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Basic key translation (handleKeyDown)
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
TEST_CASE("handleKeyDown returns translated Apple II keycode", "[keyboard][basic]") {
|
|
17
|
+
Keyboard kb;
|
|
18
|
+
// 'A' key (browser keycode 65) with no modifiers
|
|
19
|
+
// translateKeycode maps 65 -> 0x61 ('a' lowercase)
|
|
20
|
+
int result = kb.handleKeyDown(65, false, false, false, false, false);
|
|
21
|
+
// Without caps lock or shift, should return lowercase 'a' = 0x61
|
|
22
|
+
CHECK(result == 0x61);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
TEST_CASE("Letter keys A-Z map to lowercase ASCII without modifiers", "[keyboard][letters]") {
|
|
26
|
+
Keyboard kb;
|
|
27
|
+
// Browser keycodes 65-90 for A-Z
|
|
28
|
+
for (int browserKey = 65; browserKey <= 90; browserKey++) {
|
|
29
|
+
int result = kb.handleKeyDown(browserKey, false, false, false, false, false);
|
|
30
|
+
int expected = browserKey + 32; // lowercase ASCII
|
|
31
|
+
INFO("Browser keycode " << browserKey << " expected 0x" << std::hex << expected);
|
|
32
|
+
CHECK(result == expected);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Shift modifier
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
TEST_CASE("Shift+letter gives uppercase ASCII", "[keyboard][shift]") {
|
|
41
|
+
Keyboard kb;
|
|
42
|
+
// Browser keycode 65 ('A') with shift
|
|
43
|
+
int result = kb.handleKeyDown(65, true, false, false, false, false);
|
|
44
|
+
CHECK(result == 0x41); // 'A' uppercase
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
TEST_CASE("Shift modifier uppercase for all letters", "[keyboard][shift]") {
|
|
48
|
+
Keyboard kb;
|
|
49
|
+
for (int browserKey = 65; browserKey <= 90; browserKey++) {
|
|
50
|
+
int result = kb.handleKeyDown(browserKey, true, false, false, false, false);
|
|
51
|
+
int expected = browserKey; // uppercase ASCII = same as browser keycode
|
|
52
|
+
INFO("Browser keycode " << browserKey);
|
|
53
|
+
CHECK(result == expected);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
TEST_CASE("Caps lock gives uppercase without shift", "[keyboard][capslock]") {
|
|
58
|
+
Keyboard kb;
|
|
59
|
+
int result = kb.handleKeyDown(65, false, false, false, false, true);
|
|
60
|
+
CHECK(result == 0x41); // 'A' uppercase
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Control modifier
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
TEST_CASE("Ctrl+A gives control character 0x01", "[keyboard][ctrl]") {
|
|
68
|
+
Keyboard kb;
|
|
69
|
+
// Browser keycode 65 ('A'), ctrl pressed
|
|
70
|
+
int result = kb.handleKeyDown(65, false, true, false, false, false);
|
|
71
|
+
CHECK(result == 0x01);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
TEST_CASE("Ctrl+letters produce control characters 0x01-0x1A", "[keyboard][ctrl]") {
|
|
75
|
+
Keyboard kb;
|
|
76
|
+
for (int browserKey = 65; browserKey <= 90; browserKey++) {
|
|
77
|
+
int result = kb.handleKeyDown(browserKey, false, true, false, false, false);
|
|
78
|
+
int expected = browserKey - 64; // Ctrl+A=1, Ctrl+B=2, ...
|
|
79
|
+
INFO("Ctrl+" << (char)browserKey << " expected 0x" << std::hex << expected);
|
|
80
|
+
CHECK(result == expected);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Open Apple / Closed Apple (button state)
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
TEST_CASE("Alt key sets Open Apple pressed state", "[keyboard][apple]") {
|
|
89
|
+
Keyboard kb;
|
|
90
|
+
CHECK(kb.isOpenApplePressed() == false);
|
|
91
|
+
|
|
92
|
+
// Alt key down (browser keycode 18)
|
|
93
|
+
kb.handleKeyDown(18, false, false, true, false, false);
|
|
94
|
+
CHECK(kb.isOpenApplePressed() == true);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
TEST_CASE("Meta key sets Closed Apple pressed state", "[keyboard][apple]") {
|
|
98
|
+
Keyboard kb;
|
|
99
|
+
CHECK(kb.isClosedApplePressed() == false);
|
|
100
|
+
|
|
101
|
+
// Left Meta key down (browser keycode 91)
|
|
102
|
+
kb.handleKeyDown(91, false, false, false, true, false);
|
|
103
|
+
CHECK(kb.isClosedApplePressed() == true);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
TEST_CASE("Right Meta key also sets Closed Apple", "[keyboard][apple]") {
|
|
107
|
+
Keyboard kb;
|
|
108
|
+
// Right Meta key down (browser keycode 93)
|
|
109
|
+
kb.handleKeyDown(93, false, false, false, true, false);
|
|
110
|
+
CHECK(kb.isClosedApplePressed() == true);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// handleKeyUp clears button state
|
|
115
|
+
// ============================================================================
|
|
116
|
+
|
|
117
|
+
TEST_CASE("handleKeyUp clears Open Apple", "[keyboard][keyup]") {
|
|
118
|
+
Keyboard kb;
|
|
119
|
+
|
|
120
|
+
kb.handleKeyDown(18, false, false, true, false, false);
|
|
121
|
+
CHECK(kb.isOpenApplePressed() == true);
|
|
122
|
+
|
|
123
|
+
kb.handleKeyUp(18, false, false, true, false);
|
|
124
|
+
CHECK(kb.isOpenApplePressed() == false);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
TEST_CASE("handleKeyUp clears Closed Apple", "[keyboard][keyup]") {
|
|
128
|
+
Keyboard kb;
|
|
129
|
+
|
|
130
|
+
kb.handleKeyDown(91, false, false, false, true, false);
|
|
131
|
+
CHECK(kb.isClosedApplePressed() == true);
|
|
132
|
+
|
|
133
|
+
kb.handleKeyUp(91, false, false, false, true);
|
|
134
|
+
CHECK(kb.isClosedApplePressed() == false);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// reset
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
TEST_CASE("reset clears modifier states", "[keyboard][reset]") {
|
|
142
|
+
Keyboard kb;
|
|
143
|
+
|
|
144
|
+
// Set both apple buttons
|
|
145
|
+
kb.handleKeyDown(18, false, false, true, false, false);
|
|
146
|
+
kb.handleKeyDown(91, false, false, false, true, false);
|
|
147
|
+
CHECK(kb.isOpenApplePressed() == true);
|
|
148
|
+
CHECK(kb.isClosedApplePressed() == true);
|
|
149
|
+
|
|
150
|
+
kb.reset();
|
|
151
|
+
|
|
152
|
+
CHECK(kb.isOpenApplePressed() == false);
|
|
153
|
+
CHECK(kb.isClosedApplePressed() == false);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// Special keys
|
|
158
|
+
// ============================================================================
|
|
159
|
+
|
|
160
|
+
TEST_CASE("Enter key maps to CR (0x0D)", "[keyboard][special]") {
|
|
161
|
+
Keyboard kb;
|
|
162
|
+
int result = kb.handleKeyDown(13, false, false, false, false, false);
|
|
163
|
+
CHECK(result == 0x0D);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
TEST_CASE("Escape key maps to 0x1B", "[keyboard][special]") {
|
|
167
|
+
Keyboard kb;
|
|
168
|
+
int result = kb.handleKeyDown(27, false, false, false, false, false);
|
|
169
|
+
CHECK(result == 0x1B);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
TEST_CASE("Space key maps to 0x20", "[keyboard][special]") {
|
|
173
|
+
Keyboard kb;
|
|
174
|
+
int result = kb.handleKeyDown(32, false, false, false, false, false);
|
|
175
|
+
CHECK(result == 0x20);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
TEST_CASE("Left arrow maps to 0x08", "[keyboard][special]") {
|
|
179
|
+
Keyboard kb;
|
|
180
|
+
int result = kb.handleKeyDown(37, false, false, false, false, false);
|
|
181
|
+
CHECK(result == 0x08);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
TEST_CASE("Right arrow maps to 0x15", "[keyboard][special]") {
|
|
185
|
+
Keyboard kb;
|
|
186
|
+
int result = kb.handleKeyDown(39, false, false, false, false, false);
|
|
187
|
+
CHECK(result == 0x15);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
TEST_CASE("Up arrow maps to 0x0B", "[keyboard][special]") {
|
|
191
|
+
Keyboard kb;
|
|
192
|
+
int result = kb.handleKeyDown(38, false, false, false, false, false);
|
|
193
|
+
CHECK(result == 0x0B);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
TEST_CASE("Down arrow maps to 0x0A", "[keyboard][special]") {
|
|
197
|
+
Keyboard kb;
|
|
198
|
+
int result = kb.handleKeyDown(40, false, false, false, false, false);
|
|
199
|
+
CHECK(result == 0x0A);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// Unmapped keys return -1
|
|
204
|
+
// ============================================================================
|
|
205
|
+
|
|
206
|
+
TEST_CASE("Unmapped key returns -1", "[keyboard][unmapped]") {
|
|
207
|
+
Keyboard kb;
|
|
208
|
+
// F-keys and other non-mapped keys
|
|
209
|
+
int result = kb.handleKeyDown(112, false, false, false, false, false); // F1
|
|
210
|
+
CHECK(result == -1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
TEST_CASE("Pure modifier keys return -1", "[keyboard][unmapped]") {
|
|
214
|
+
Keyboard kb;
|
|
215
|
+
// Shift key (16) returns -1
|
|
216
|
+
CHECK(kb.handleKeyDown(16, true, false, false, false, false) == -1);
|
|
217
|
+
// Ctrl key (17) returns -1
|
|
218
|
+
CHECK(kb.handleKeyDown(17, false, true, false, false, false) == -1);
|
|
219
|
+
// Alt key (18) returns -1 (but sets Open Apple state)
|
|
220
|
+
CHECK(kb.handleKeyDown(18, false, false, true, false, false) == -1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ============================================================================
|
|
224
|
+
// charToAppleKey
|
|
225
|
+
// ============================================================================
|
|
226
|
+
|
|
227
|
+
TEST_CASE("charToAppleKey converts printable ASCII", "[keyboard][charToAppleKey]") {
|
|
228
|
+
CHECK(charToAppleKey(0x20) == 0x20); // space
|
|
229
|
+
CHECK(charToAppleKey(0x41) == 0x41); // 'A'
|
|
230
|
+
CHECK(charToAppleKey(0x61) == 0x61); // 'a'
|
|
231
|
+
CHECK(charToAppleKey(0x7E) == 0x7E); // '~'
|
|
232
|
+
CHECK(charToAppleKey(0x30) == 0x30); // '0'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
TEST_CASE("charToAppleKey converts newline to CR", "[keyboard][charToAppleKey]") {
|
|
236
|
+
CHECK(charToAppleKey(0x0A) == 0x0D); // LF -> CR
|
|
237
|
+
CHECK(charToAppleKey(0x0D) == 0x0D); // CR -> CR
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
TEST_CASE("charToAppleKey converts tab", "[keyboard][charToAppleKey]") {
|
|
241
|
+
CHECK(charToAppleKey(0x09) == 0x09);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
TEST_CASE("charToAppleKey returns -1 for unmappable characters", "[keyboard][charToAppleKey]") {
|
|
245
|
+
CHECK(charToAppleKey(0x00) == -1);
|
|
246
|
+
CHECK(charToAppleKey(0x01) == -1);
|
|
247
|
+
CHECK(charToAppleKey(0x7F) == -1);
|
|
248
|
+
CHECK(charToAppleKey(0x100) == -1); // Beyond ASCII
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// Shift+number row
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
TEST_CASE("Shift+number produces correct symbols", "[keyboard][shift_symbols]") {
|
|
256
|
+
Keyboard kb;
|
|
257
|
+
CHECK(kb.handleKeyDown(49, true, false, false, false, false) == 0x21); // 1 -> !
|
|
258
|
+
CHECK(kb.handleKeyDown(50, true, false, false, false, false) == 0x40); // 2 -> @
|
|
259
|
+
CHECK(kb.handleKeyDown(51, true, false, false, false, false) == 0x23); // 3 -> #
|
|
260
|
+
CHECK(kb.handleKeyDown(48, true, false, false, false, false) == 0x29); // 0 -> )
|
|
261
|
+
CHECK(kb.handleKeyDown(57, true, false, false, false, false) == 0x28); // 9 -> (
|
|
262
|
+
}
|