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,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thunderclock Plus Card Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the Thunderclock Plus clock card implementation to verify:
|
|
5
|
+
* 1. ROM signature bytes for ProDOS detection
|
|
6
|
+
* 2. I/O register behavior
|
|
7
|
+
* 3. Time data serialization
|
|
8
|
+
* 4. Clock/strobe state machine
|
|
9
|
+
*
|
|
10
|
+
* ProDOS scans slots looking for specific ROM signature bytes:
|
|
11
|
+
* - $Cn00: $08 (PHP instruction)
|
|
12
|
+
* - $Cn02: $28 (signature byte)
|
|
13
|
+
* - $Cn04: $58 (signature byte)
|
|
14
|
+
* - $Cn06: $70 (signature byte)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#include <iostream>
|
|
18
|
+
#include <iomanip>
|
|
19
|
+
#include <cstdint>
|
|
20
|
+
#include <vector>
|
|
21
|
+
#include <string>
|
|
22
|
+
#include <sstream>
|
|
23
|
+
#include <ctime>
|
|
24
|
+
#include <stdexcept>
|
|
25
|
+
|
|
26
|
+
// Include the Thunderclock card implementation
|
|
27
|
+
#include "cards/thunderclock_card.hpp"
|
|
28
|
+
|
|
29
|
+
// Test result tracking
|
|
30
|
+
static int testsRun = 0;
|
|
31
|
+
static int testsPassed = 0;
|
|
32
|
+
static int testsFailed = 0;
|
|
33
|
+
|
|
34
|
+
#define TEST(name) \
|
|
35
|
+
void test_##name(); \
|
|
36
|
+
struct TestRunner_##name { \
|
|
37
|
+
TestRunner_##name() { \
|
|
38
|
+
std::cout << "Running: " << #name << "... "; \
|
|
39
|
+
testsRun++; \
|
|
40
|
+
try { \
|
|
41
|
+
test_##name(); \
|
|
42
|
+
testsPassed++; \
|
|
43
|
+
std::cout << "\033[32mPASSED\033[0m\n"; \
|
|
44
|
+
} catch (const std::exception& e) { \
|
|
45
|
+
testsFailed++; \
|
|
46
|
+
std::cout << "\033[31mFAILED: " << e.what() << "\033[0m\n"; \
|
|
47
|
+
} \
|
|
48
|
+
} \
|
|
49
|
+
} testRunner_##name; \
|
|
50
|
+
void test_##name()
|
|
51
|
+
|
|
52
|
+
#define ASSERT_EQ(expected, actual) \
|
|
53
|
+
if ((expected) != (actual)) { \
|
|
54
|
+
throw std::runtime_error( \
|
|
55
|
+
"Expected " + std::to_string(expected) + \
|
|
56
|
+
" but got " + std::to_string(actual)); \
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#define ASSERT_EQ_HEX(expected, actual) \
|
|
60
|
+
if ((expected) != (actual)) { \
|
|
61
|
+
std::stringstream ss; \
|
|
62
|
+
ss << "Expected $" << std::hex << std::uppercase << (int)(expected) \
|
|
63
|
+
<< " but got $" << (int)(actual); \
|
|
64
|
+
throw std::runtime_error(ss.str()); \
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#define ASSERT_TRUE(condition) \
|
|
68
|
+
if (!(condition)) { \
|
|
69
|
+
throw std::runtime_error("Assertion failed: " #condition); \
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Control register flags
|
|
73
|
+
static constexpr uint8_t FLAG_CLOCK = 0x02;
|
|
74
|
+
static constexpr uint8_t FLAG_STROBE = 0x04;
|
|
75
|
+
static constexpr uint8_t CMD_REGHOLD = 0x00;
|
|
76
|
+
static constexpr uint8_t CMD_REGSHIFT = 0x20;
|
|
77
|
+
static constexpr uint8_t CMD_TIMED = 0xA0;
|
|
78
|
+
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// ROM Signature Tests
|
|
81
|
+
// ============================================================================
|
|
82
|
+
|
|
83
|
+
TEST(rom_signature_prodos_detection) {
|
|
84
|
+
// ProDOS looks for these bytes at specific offsets to detect a clock card
|
|
85
|
+
a2e::ThunderclockCard card;
|
|
86
|
+
|
|
87
|
+
// $Cn00: $08 (PHP instruction)
|
|
88
|
+
ASSERT_EQ_HEX(0x08, card.readROM(0x00));
|
|
89
|
+
|
|
90
|
+
// $Cn02: $28 (PLP instruction - signature byte)
|
|
91
|
+
ASSERT_EQ_HEX(0x28, card.readROM(0x02));
|
|
92
|
+
|
|
93
|
+
// $Cn04: $58 (CLI instruction - signature byte)
|
|
94
|
+
ASSERT_EQ_HEX(0x58, card.readROM(0x04));
|
|
95
|
+
|
|
96
|
+
// $Cn06: $70 (BVS instruction - signature byte)
|
|
97
|
+
ASSERT_EQ_HEX(0x70, card.readROM(0x06));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
TEST(rom_first_instruction_sequence) {
|
|
101
|
+
// Verify the first few bytes form valid 6502 code
|
|
102
|
+
a2e::ThunderclockCard card;
|
|
103
|
+
|
|
104
|
+
// Dump first 16 bytes for inspection
|
|
105
|
+
std::cout << "\n ROM bytes: ";
|
|
106
|
+
for (int i = 0; i < 16; i++) {
|
|
107
|
+
std::cout << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
|
|
108
|
+
<< (int)card.readROM(i) << " ";
|
|
109
|
+
}
|
|
110
|
+
std::cout << std::dec << "\n ";
|
|
111
|
+
|
|
112
|
+
// The ROM should start with PHP ($08) to save processor status
|
|
113
|
+
ASSERT_EQ_HEX(0x08, card.readROM(0x00));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
TEST(rom_size_and_expansion) {
|
|
117
|
+
a2e::ThunderclockCard card;
|
|
118
|
+
|
|
119
|
+
// Card should have ROM
|
|
120
|
+
ASSERT_TRUE(card.hasROM());
|
|
121
|
+
|
|
122
|
+
// Card should have expansion ROM
|
|
123
|
+
ASSERT_TRUE(card.hasExpansionROM());
|
|
124
|
+
|
|
125
|
+
// Read expansion ROM - should return valid data, not 0xFF
|
|
126
|
+
uint8_t expByte = card.readExpansionROM(0x00);
|
|
127
|
+
std::cout << "\n Expansion ROM[0] = $" << std::hex << (int)expByte << std::dec << "\n ";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// I/O Register Tests
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
TEST(io_read_initial_state) {
|
|
135
|
+
a2e::ThunderclockCard card;
|
|
136
|
+
|
|
137
|
+
// Initial register state should be 0
|
|
138
|
+
ASSERT_EQ_HEX(0x00, card.readIO(0x00));
|
|
139
|
+
|
|
140
|
+
// All I/O addresses should return the same register
|
|
141
|
+
for (int offset = 0; offset < 16; offset++) {
|
|
142
|
+
ASSERT_EQ_HEX(0x00, card.readIO(offset));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
TEST(io_peek_matches_read) {
|
|
147
|
+
a2e::ThunderclockCard card;
|
|
148
|
+
|
|
149
|
+
// Peek should match read
|
|
150
|
+
ASSERT_EQ(card.readIO(0x00), card.peekIO(0x00));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
TEST(io_write_does_not_directly_change_register) {
|
|
154
|
+
a2e::ThunderclockCard card;
|
|
155
|
+
|
|
156
|
+
// Writing to I/O should not directly change the read value
|
|
157
|
+
// (it controls the state machine, not the data register)
|
|
158
|
+
card.writeIO(0x00, 0x55);
|
|
159
|
+
// Register value depends on state machine, not direct write
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// Time Data Command Tests
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
TEST(time_command_loads_data) {
|
|
167
|
+
a2e::ThunderclockCard card;
|
|
168
|
+
|
|
169
|
+
// Issue CMD_TIMED with strobe rising edge
|
|
170
|
+
card.writeIO(0x00, 0x00); // Ensure strobe is low
|
|
171
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE); // Strobe rising edge with time command
|
|
172
|
+
|
|
173
|
+
// Now clock out the first bit
|
|
174
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE); // Clock low
|
|
175
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK); // Clock rising edge
|
|
176
|
+
|
|
177
|
+
// Read should now have bit 7 set to the first data bit
|
|
178
|
+
uint8_t value = card.readIO(0x00);
|
|
179
|
+
std::cout << "\n After first clock: register = $" << std::hex << (int)value << std::dec << "\n ";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
TEST(time_data_40_bits) {
|
|
183
|
+
a2e::ThunderclockCard card;
|
|
184
|
+
|
|
185
|
+
// Issue CMD_TIMED with strobe rising edge
|
|
186
|
+
card.writeIO(0x00, 0x00);
|
|
187
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
188
|
+
|
|
189
|
+
// Clock out all 40 bits
|
|
190
|
+
std::vector<int> bits;
|
|
191
|
+
for (int i = 0; i < 40; i++) {
|
|
192
|
+
// Clock rising edge
|
|
193
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE); // Clock low
|
|
194
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK); // Clock high
|
|
195
|
+
|
|
196
|
+
uint8_t value = card.readIO(0x00);
|
|
197
|
+
bits.push_back((value & 0x80) ? 1 : 0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Print the bits
|
|
201
|
+
std::cout << "\n 40 bits: ";
|
|
202
|
+
for (int i = 0; i < 40; i++) {
|
|
203
|
+
std::cout << bits[i];
|
|
204
|
+
if ((i + 1) % 4 == 0) std::cout << " ";
|
|
205
|
+
}
|
|
206
|
+
std::cout << "\n ";
|
|
207
|
+
|
|
208
|
+
// Decode the bits
|
|
209
|
+
// Month (4 bits), weekday (4 bits), day (8 bits BCD), hour (8 bits BCD), min (8 bits BCD), sec (8 bits BCD)
|
|
210
|
+
int month = (bits[0] << 3) | (bits[1] << 2) | (bits[2] << 1) | bits[3];
|
|
211
|
+
int weekday = (bits[4] << 3) | (bits[5] << 2) | (bits[6] << 1) | bits[7];
|
|
212
|
+
int day_tens = (bits[8] << 3) | (bits[9] << 2) | (bits[10] << 1) | bits[11];
|
|
213
|
+
int day_ones = (bits[12] << 3) | (bits[13] << 2) | (bits[14] << 1) | bits[15];
|
|
214
|
+
int day = day_tens * 10 + day_ones;
|
|
215
|
+
int hour_tens = (bits[16] << 3) | (bits[17] << 2) | (bits[18] << 1) | bits[19];
|
|
216
|
+
int hour_ones = (bits[20] << 3) | (bits[21] << 2) | (bits[22] << 1) | bits[23];
|
|
217
|
+
int hour = hour_tens * 10 + hour_ones;
|
|
218
|
+
int min_tens = (bits[24] << 3) | (bits[25] << 2) | (bits[26] << 1) | bits[27];
|
|
219
|
+
int min_ones = (bits[28] << 3) | (bits[29] << 2) | (bits[30] << 1) | bits[31];
|
|
220
|
+
int minute = min_tens * 10 + min_ones;
|
|
221
|
+
int sec_tens = (bits[32] << 3) | (bits[33] << 2) | (bits[34] << 1) | bits[35];
|
|
222
|
+
int sec_ones = (bits[36] << 3) | (bits[37] << 2) | (bits[38] << 1) | bits[39];
|
|
223
|
+
int second = sec_tens * 10 + sec_ones;
|
|
224
|
+
|
|
225
|
+
std::cout << " Decoded: month=" << month << " weekday=" << weekday
|
|
226
|
+
<< " day=" << day << " hour=" << hour << ":" << minute << ":" << second << "\n ";
|
|
227
|
+
|
|
228
|
+
// Get current time to compare
|
|
229
|
+
std::time_t now = std::time(nullptr);
|
|
230
|
+
std::tm* tm = std::localtime(&now);
|
|
231
|
+
|
|
232
|
+
// Month should match (0-11)
|
|
233
|
+
ASSERT_EQ(tm->tm_mon, month);
|
|
234
|
+
|
|
235
|
+
// Weekday should match (0-6)
|
|
236
|
+
ASSERT_EQ(tm->tm_wday, weekday);
|
|
237
|
+
|
|
238
|
+
// Day should be close (might change during test)
|
|
239
|
+
ASSERT_TRUE(day >= 1 && day <= 31);
|
|
240
|
+
|
|
241
|
+
// Hour should match
|
|
242
|
+
ASSERT_EQ(tm->tm_hour, hour);
|
|
243
|
+
|
|
244
|
+
// Minute should be close (might change during test)
|
|
245
|
+
ASSERT_TRUE(minute >= 0 && minute <= 59);
|
|
246
|
+
|
|
247
|
+
// Second should be valid
|
|
248
|
+
ASSERT_TRUE(second >= 0 && second <= 59);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// State Machine Tests
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
TEST(strobe_edge_detection) {
|
|
256
|
+
a2e::ThunderclockCard card;
|
|
257
|
+
|
|
258
|
+
// Strobe should only trigger on rising edge
|
|
259
|
+
card.writeIO(0x00, 0x00); // Low
|
|
260
|
+
card.writeIO(0x00, FLAG_STROBE); // Rising edge - should trigger
|
|
261
|
+
card.writeIO(0x00, FLAG_STROBE); // Still high - should NOT trigger again
|
|
262
|
+
card.writeIO(0x00, 0x00); // Falling edge
|
|
263
|
+
card.writeIO(0x00, FLAG_STROBE); // Rising edge again - should trigger
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
TEST(clock_edge_detection) {
|
|
267
|
+
a2e::ThunderclockCard card;
|
|
268
|
+
|
|
269
|
+
// Setup: issue time command
|
|
270
|
+
card.writeIO(0x00, 0x00);
|
|
271
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
272
|
+
|
|
273
|
+
// Clock should only shift on rising edge
|
|
274
|
+
uint8_t val1 = card.readIO(0x00);
|
|
275
|
+
|
|
276
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK); // Rising edge
|
|
277
|
+
uint8_t val2 = card.readIO(0x00);
|
|
278
|
+
|
|
279
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK); // Still high - no shift
|
|
280
|
+
uint8_t val3 = card.readIO(0x00);
|
|
281
|
+
|
|
282
|
+
// val2 and val3 should be the same (no shift on holding high)
|
|
283
|
+
ASSERT_EQ(val2, val3);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
TEST(reset_clears_state) {
|
|
287
|
+
a2e::ThunderclockCard card;
|
|
288
|
+
|
|
289
|
+
// Setup some state
|
|
290
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
291
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
292
|
+
|
|
293
|
+
// Reset
|
|
294
|
+
card.reset();
|
|
295
|
+
|
|
296
|
+
// Register should be 0 after reset
|
|
297
|
+
ASSERT_EQ_HEX(0x00, card.readIO(0x00));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// Card Information Tests
|
|
302
|
+
// ============================================================================
|
|
303
|
+
|
|
304
|
+
TEST(card_name) {
|
|
305
|
+
a2e::ThunderclockCard card;
|
|
306
|
+
std::string name = card.getName();
|
|
307
|
+
ASSERT_TRUE(name == "Thunderclock");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
TEST(preferred_slot) {
|
|
311
|
+
a2e::ThunderclockCard card;
|
|
312
|
+
// Thunderclock is typically in slot 5 or 7
|
|
313
|
+
uint8_t slot = card.getPreferredSlot();
|
|
314
|
+
ASSERT_TRUE(slot == 5 || slot == 7);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ============================================================================
|
|
318
|
+
// ProDOS Driver Interaction Simulation
|
|
319
|
+
// ============================================================================
|
|
320
|
+
|
|
321
|
+
TEST(prodos_driver_read_sequence) {
|
|
322
|
+
// Simulate how ProDOS reads time from the Thunderclock
|
|
323
|
+
// The driver in ROM does:
|
|
324
|
+
// 1. Write CMD_TIMED with strobe rising edge to start reading
|
|
325
|
+
// 2. Clock out 32 bits (ProDOS only needs date/time, not seconds)
|
|
326
|
+
// 3. Convert to ProDOS date/time format
|
|
327
|
+
|
|
328
|
+
a2e::ThunderclockCard card;
|
|
329
|
+
|
|
330
|
+
// Step 1: Issue time read command
|
|
331
|
+
card.writeIO(0x00, 0x00); // Clear
|
|
332
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE); // Strobe rising with time cmd
|
|
333
|
+
|
|
334
|
+
// Step 2: Clock out 32 bits (ProDOS format)
|
|
335
|
+
uint32_t rawTime = 0;
|
|
336
|
+
for (int i = 0; i < 32; i++) {
|
|
337
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE); // Clock low
|
|
338
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK); // Clock rising
|
|
339
|
+
|
|
340
|
+
uint8_t value = card.readIO(0x00);
|
|
341
|
+
rawTime = (rawTime << 1) | ((value & 0x80) ? 1 : 0);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
std::cout << "\n Raw 32-bit time: $" << std::hex << rawTime << std::dec << "\n ";
|
|
345
|
+
|
|
346
|
+
// Decode: first 8 bits = month(4) + weekday(4), next 8 = day BCD, next 8 = hour BCD, next 8 = min BCD
|
|
347
|
+
int month = (rawTime >> 28) & 0x0F;
|
|
348
|
+
int weekday = (rawTime >> 24) & 0x0F;
|
|
349
|
+
int day = ((rawTime >> 20) & 0x0F) * 10 + ((rawTime >> 16) & 0x0F);
|
|
350
|
+
int hour = ((rawTime >> 12) & 0x0F) * 10 + ((rawTime >> 8) & 0x0F);
|
|
351
|
+
int minute = ((rawTime >> 4) & 0x0F) * 10 + (rawTime & 0x0F);
|
|
352
|
+
|
|
353
|
+
std::cout << " ProDOS format: " << (month + 1) << "/" << day << " " << hour << ":" << minute << "\n ";
|
|
354
|
+
|
|
355
|
+
// Verify values are in valid ranges
|
|
356
|
+
ASSERT_TRUE(month >= 0 && month <= 11);
|
|
357
|
+
ASSERT_TRUE(day >= 1 && day <= 31);
|
|
358
|
+
ASSERT_TRUE(hour >= 0 && hour <= 23);
|
|
359
|
+
ASSERT_TRUE(minute >= 0 && minute <= 59);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ============================================================================
|
|
363
|
+
// ROM Disassembly Analysis
|
|
364
|
+
// ============================================================================
|
|
365
|
+
|
|
366
|
+
TEST(rom_entry_point_analysis) {
|
|
367
|
+
a2e::ThunderclockCard card;
|
|
368
|
+
|
|
369
|
+
std::cout << "\n ROM Entry Point Disassembly:\n";
|
|
370
|
+
|
|
371
|
+
// Disassemble first 32 bytes
|
|
372
|
+
const char* mnemonics[] = {
|
|
373
|
+
"PHP", "SEI", "PLP", "BIT $FF58", "", "", "BVS +5", "",
|
|
374
|
+
"SEC", "BCS +1", "", "CLC", "CLV", "PHP", "SEI", "PHA"
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
for (int i = 0; i < 16; i++) {
|
|
378
|
+
uint8_t byte = card.readROM(i);
|
|
379
|
+
std::cout << " $" << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << i
|
|
380
|
+
<< ": " << std::setw(2) << (int)byte << std::dec;
|
|
381
|
+
if (i < 16 && mnemonics[i][0]) {
|
|
382
|
+
std::cout << " ; " << mnemonics[i];
|
|
383
|
+
}
|
|
384
|
+
std::cout << "\n";
|
|
385
|
+
}
|
|
386
|
+
std::cout << " ";
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
TEST(expansion_rom_entry_points) {
|
|
390
|
+
a2e::ThunderclockCard card;
|
|
391
|
+
|
|
392
|
+
// The expansion ROM at $C800 should have valid code
|
|
393
|
+
std::cout << "\n Expansion ROM ($C800) first 16 bytes:\n ";
|
|
394
|
+
for (int i = 0; i < 16; i++) {
|
|
395
|
+
std::cout << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
|
|
396
|
+
<< (int)card.readExpansionROM(i) << " ";
|
|
397
|
+
}
|
|
398
|
+
std::cout << std::dec << "\n ";
|
|
399
|
+
|
|
400
|
+
// Check that expansion ROM isn't all zeros or 0xFF
|
|
401
|
+
bool allZero = true;
|
|
402
|
+
bool allFF = true;
|
|
403
|
+
for (int i = 0; i < 256; i++) {
|
|
404
|
+
uint8_t byte = card.readExpansionROM(i);
|
|
405
|
+
if (byte != 0x00) allZero = false;
|
|
406
|
+
if (byte != 0xFF) allFF = false;
|
|
407
|
+
}
|
|
408
|
+
ASSERT_TRUE(!allZero);
|
|
409
|
+
ASSERT_TRUE(!allFF);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ============================================================================
|
|
413
|
+
// Verify ProDOS-specific behavior
|
|
414
|
+
// ============================================================================
|
|
415
|
+
|
|
416
|
+
TEST(prodos_reads_rom_signature_correctly) {
|
|
417
|
+
// ProDOS reads specific offsets to detect clock card
|
|
418
|
+
a2e::ThunderclockCard card;
|
|
419
|
+
|
|
420
|
+
// These must match exactly for ProDOS to recognize the card
|
|
421
|
+
std::cout << "\n ProDOS signature check:\n";
|
|
422
|
+
std::cout << " $Cn00 = $" << std::hex << (int)card.readROM(0x00) << " (expected $08)\n";
|
|
423
|
+
std::cout << " $Cn02 = $" << std::hex << (int)card.readROM(0x02) << " (expected $28)\n";
|
|
424
|
+
std::cout << " $Cn04 = $" << std::hex << (int)card.readROM(0x04) << " (expected $58)\n";
|
|
425
|
+
std::cout << " $Cn06 = $" << std::hex << (int)card.readROM(0x06) << " (expected $70)\n";
|
|
426
|
+
std::cout << std::dec << " ";
|
|
427
|
+
|
|
428
|
+
ASSERT_EQ_HEX(0x08, card.readROM(0x00));
|
|
429
|
+
ASSERT_EQ_HEX(0x28, card.readROM(0x02));
|
|
430
|
+
ASSERT_EQ_HEX(0x58, card.readROM(0x04));
|
|
431
|
+
ASSERT_EQ_HEX(0x70, card.readROM(0x06));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
TEST(io_address_all_offsets_return_same_register) {
|
|
435
|
+
// The Thunderclock should return the same register from all I/O offsets
|
|
436
|
+
a2e::ThunderclockCard card;
|
|
437
|
+
|
|
438
|
+
// Set up some state
|
|
439
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
440
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
441
|
+
|
|
442
|
+
uint8_t expected = card.readIO(0x00);
|
|
443
|
+
|
|
444
|
+
// All 16 I/O addresses should return the same value
|
|
445
|
+
for (int offset = 0; offset < 16; offset++) {
|
|
446
|
+
ASSERT_EQ(expected, card.readIO(offset));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
TEST(multiple_time_reads_without_reset) {
|
|
451
|
+
// Verify that multiple time read commands work correctly
|
|
452
|
+
a2e::ThunderclockCard card;
|
|
453
|
+
|
|
454
|
+
for (int iteration = 0; iteration < 3; iteration++) {
|
|
455
|
+
// Issue time read command
|
|
456
|
+
card.writeIO(0x00, 0x00);
|
|
457
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
458
|
+
|
|
459
|
+
// Read 8 bits
|
|
460
|
+
uint8_t byte = 0;
|
|
461
|
+
for (int bit = 0; bit < 8; bit++) {
|
|
462
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
463
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
464
|
+
uint8_t value = card.readIO(0x00);
|
|
465
|
+
byte = (byte << 1) | ((value & 0x80) ? 1 : 0);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
std::cout << "\n Iteration " << iteration << ": first byte = $"
|
|
469
|
+
<< std::hex << (int)byte << std::dec;
|
|
470
|
+
}
|
|
471
|
+
std::cout << "\n ";
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
TEST(read_before_any_clocking) {
|
|
475
|
+
// Test what happens when you read the register before any clocking
|
|
476
|
+
// This is important because ProDOS may read to check status
|
|
477
|
+
a2e::ThunderclockCard card;
|
|
478
|
+
|
|
479
|
+
// Read immediately after construction (after reset)
|
|
480
|
+
uint8_t value = card.readIO(0x00);
|
|
481
|
+
std::cout << "\n After construction (no commands): $" << std::hex << (int)value << std::dec << "\n";
|
|
482
|
+
|
|
483
|
+
// Issue strobe only (no clock yet)
|
|
484
|
+
card.writeIO(0x00, 0x00);
|
|
485
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
486
|
+
value = card.readIO(0x00);
|
|
487
|
+
std::cout << " After strobe (time command, no clock): $" << std::hex << (int)value << std::dec << "\n";
|
|
488
|
+
|
|
489
|
+
// Now clock one bit
|
|
490
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
491
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
492
|
+
value = card.readIO(0x00);
|
|
493
|
+
std::cout << " After first clock: $" << std::hex << (int)value << std::dec << "\n ";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
TEST(verify_bcd_encoding) {
|
|
497
|
+
// Verify BCD encoding is correct for various values
|
|
498
|
+
a2e::ThunderclockCard card;
|
|
499
|
+
|
|
500
|
+
// Issue time read command
|
|
501
|
+
card.writeIO(0x00, 0x00);
|
|
502
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
503
|
+
|
|
504
|
+
// Skip first 8 bits (month + weekday)
|
|
505
|
+
for (int i = 0; i < 8; i++) {
|
|
506
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
507
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Read day (8 bits BCD)
|
|
511
|
+
uint8_t dayBCD = 0;
|
|
512
|
+
for (int i = 0; i < 8; i++) {
|
|
513
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
514
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
515
|
+
dayBCD = (dayBCD << 1) | ((card.readIO(0x00) & 0x80) ? 1 : 0);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
int dayTens = (dayBCD >> 4) & 0x0F;
|
|
519
|
+
int dayOnes = dayBCD & 0x0F;
|
|
520
|
+
int day = dayTens * 10 + dayOnes;
|
|
521
|
+
|
|
522
|
+
std::cout << "\n Day BCD: $" << std::hex << (int)dayBCD << std::dec
|
|
523
|
+
<< " = " << day << "\n ";
|
|
524
|
+
|
|
525
|
+
// Day should be valid (1-31)
|
|
526
|
+
ASSERT_TRUE(day >= 1 && day <= 31);
|
|
527
|
+
// BCD digits should be 0-9
|
|
528
|
+
ASSERT_TRUE(dayTens <= 3);
|
|
529
|
+
ASSERT_TRUE(dayOnes <= 9);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ============================================================================
|
|
533
|
+
// Main
|
|
534
|
+
// ============================================================================
|
|
535
|
+
|
|
536
|
+
int main() {
|
|
537
|
+
std::cout << "Thunderclock Plus Card Tests\n";
|
|
538
|
+
std::cout << "============================\n\n";
|
|
539
|
+
|
|
540
|
+
// Tests run automatically via static initializers
|
|
541
|
+
|
|
542
|
+
std::cout << "\n============================\n";
|
|
543
|
+
std::cout << "Results: " << testsPassed << "/" << testsRun << " passed";
|
|
544
|
+
if (testsFailed > 0) {
|
|
545
|
+
std::cout << " (" << testsFailed << " failed)";
|
|
546
|
+
}
|
|
547
|
+
std::cout << "\n";
|
|
548
|
+
|
|
549
|
+
return testsFailed > 0 ? 1 : 0;
|
|
550
|
+
}
|