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,291 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* trace-panel.js - CPU instruction execution trace panel
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BaseWindow } from "../windows/base-window.js";
|
|
9
|
+
import { getSymbolInfo } from "./symbols.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Cached opcode mnemonics from WASM disassembler (populated on first use)
|
|
13
|
+
*/
|
|
14
|
+
let MNEMONICS = null;
|
|
15
|
+
let ADDR_MODES = null;
|
|
16
|
+
|
|
17
|
+
// AddrMode enum values matching disassembler.hpp
|
|
18
|
+
const MODE_IMP = 0;
|
|
19
|
+
const MODE_ACC = 1;
|
|
20
|
+
const MODE_IMM = 2;
|
|
21
|
+
const MODE_ZP = 3;
|
|
22
|
+
const MODE_ZPX = 4;
|
|
23
|
+
const MODE_ZPY = 5;
|
|
24
|
+
const MODE_ABS = 6;
|
|
25
|
+
const MODE_ABX = 7;
|
|
26
|
+
const MODE_ABY = 8;
|
|
27
|
+
const MODE_IND = 9;
|
|
28
|
+
const MODE_IZX = 10;
|
|
29
|
+
const MODE_IZY = 11;
|
|
30
|
+
const MODE_REL = 12;
|
|
31
|
+
const MODE_ZPI = 13;
|
|
32
|
+
const MODE_AIX = 14;
|
|
33
|
+
const MODE_ZPR = 15;
|
|
34
|
+
|
|
35
|
+
function getMnemonicTable(wasmModule) {
|
|
36
|
+
if (MNEMONICS) return MNEMONICS;
|
|
37
|
+
MNEMONICS = new Array(256);
|
|
38
|
+
for (let i = 0; i < 256; i++) {
|
|
39
|
+
const ptr = wasmModule._getOpcodeMnemonic(i);
|
|
40
|
+
MNEMONICS[i] = ptr ? wasmModule.UTF8ToString(ptr) : "???";
|
|
41
|
+
}
|
|
42
|
+
return MNEMONICS;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getAddrModeTable(wasmModule) {
|
|
46
|
+
if (ADDR_MODES) return ADDR_MODES;
|
|
47
|
+
if (!wasmModule._getOpcodeAddressingMode) return null;
|
|
48
|
+
ADDR_MODES = new Uint8Array(256);
|
|
49
|
+
for (let i = 0; i < 256; i++) {
|
|
50
|
+
ADDR_MODES[i] = wasmModule._getOpcodeAddressingMode(i);
|
|
51
|
+
}
|
|
52
|
+
return ADDR_MODES;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* TracePanelWindow - Displays instruction trace from the WASM ring buffer
|
|
57
|
+
* with virtual scrolling for performance.
|
|
58
|
+
*/
|
|
59
|
+
export class TracePanelWindow extends BaseWindow {
|
|
60
|
+
constructor(wasmModule) {
|
|
61
|
+
super({
|
|
62
|
+
id: "trace-panel",
|
|
63
|
+
title: "Instruction Trace",
|
|
64
|
+
minWidth: 480,
|
|
65
|
+
minHeight: 300,
|
|
66
|
+
defaultWidth: 680,
|
|
67
|
+
defaultHeight: 400,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.wasmModule = wasmModule;
|
|
71
|
+
this.ROW_HEIGHT = 14;
|
|
72
|
+
this.scrollTop = 0;
|
|
73
|
+
this.filterAddr = null; // Optional filter by address range
|
|
74
|
+
this.recording = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
renderContent() {
|
|
78
|
+
return `
|
|
79
|
+
<div class="trace-panel-content">
|
|
80
|
+
<div class="trace-toolbar">
|
|
81
|
+
<label class="trace-toggle">
|
|
82
|
+
<input type="checkbox" id="trace-enabled">
|
|
83
|
+
<span>Record</span>
|
|
84
|
+
</label>
|
|
85
|
+
<button class="trace-clear-btn" id="trace-clear">Clear</button>
|
|
86
|
+
<span class="trace-count" id="trace-count">0 entries</span>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="trace-header">
|
|
89
|
+
<span class="trace-col-cycle">Cycle</span>
|
|
90
|
+
<span class="trace-col-pc">PC</span>
|
|
91
|
+
<span class="trace-col-bytes">Bytes</span>
|
|
92
|
+
<span class="trace-col-mnemonic">Mnem</span>
|
|
93
|
+
<span class="trace-col-operand">Operand</span>
|
|
94
|
+
<span class="trace-col-regs">A X Y SP NV-BDIZC</span>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="trace-scroll-container" id="trace-scroll">
|
|
97
|
+
<div class="trace-scroll-spacer" id="trace-spacer"></div>
|
|
98
|
+
<div class="trace-rows" id="trace-rows"></div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setupContentEventListeners() {
|
|
105
|
+
const enabledCheck = this.contentElement.querySelector("#trace-enabled");
|
|
106
|
+
if (enabledCheck) {
|
|
107
|
+
enabledCheck.addEventListener("change", () => {
|
|
108
|
+
this.recording = enabledCheck.checked;
|
|
109
|
+
if (this.wasmModule._setTraceEnabled) {
|
|
110
|
+
this.wasmModule._setTraceEnabled(enabledCheck.checked);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const clearBtn = this.contentElement.querySelector("#trace-clear");
|
|
116
|
+
if (clearBtn) {
|
|
117
|
+
clearBtn.addEventListener("click", () => {
|
|
118
|
+
if (this.wasmModule._clearTrace) {
|
|
119
|
+
this.wasmModule._clearTrace();
|
|
120
|
+
this.renderVisibleRows();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const scrollContainer = this.contentElement.querySelector("#trace-scroll");
|
|
126
|
+
if (scrollContainer) {
|
|
127
|
+
scrollContainer.addEventListener("scroll", () => {
|
|
128
|
+
this.scrollTop = scrollContainer.scrollTop;
|
|
129
|
+
this.renderVisibleRows();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
create() {
|
|
135
|
+
super.create();
|
|
136
|
+
this.setupContentEventListeners();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
update(wasmModule) {
|
|
140
|
+
this.wasmModule = wasmModule;
|
|
141
|
+
|
|
142
|
+
const countEl = this.contentElement.querySelector("#trace-count");
|
|
143
|
+
if (countEl && this.wasmModule._getTraceCount) {
|
|
144
|
+
const count = this.wasmModule._getTraceCount();
|
|
145
|
+
countEl.textContent = `${count} entries`;
|
|
146
|
+
|
|
147
|
+
// Auto-scroll to latest entry when recording
|
|
148
|
+
if (this.recording && count > 0) {
|
|
149
|
+
const container = this.contentElement.querySelector("#trace-scroll");
|
|
150
|
+
if (container) {
|
|
151
|
+
const totalHeight = count * this.ROW_HEIGHT;
|
|
152
|
+
container.scrollTop = totalHeight - container.clientHeight;
|
|
153
|
+
this.scrollTop = container.scrollTop;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.renderVisibleRows();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
formatOperand(mode, op1, op2, pc, len) {
|
|
162
|
+
const b = this.hex2(op1);
|
|
163
|
+
const w = this.hex2(op2) + this.hex2(op1);
|
|
164
|
+
switch (mode) {
|
|
165
|
+
case MODE_IMP: return "";
|
|
166
|
+
case MODE_ACC: return "A";
|
|
167
|
+
case MODE_IMM: return `#$${b}`;
|
|
168
|
+
case MODE_ZP: return `$${b}`;
|
|
169
|
+
case MODE_ZPX: return `$${b},X`;
|
|
170
|
+
case MODE_ZPY: return `$${b},Y`;
|
|
171
|
+
case MODE_ABS: return `$${w}`;
|
|
172
|
+
case MODE_ABX: return `$${w},X`;
|
|
173
|
+
case MODE_ABY: return `$${w},Y`;
|
|
174
|
+
case MODE_IND: return `($${w})`;
|
|
175
|
+
case MODE_IZX: return `($${b},X)`;
|
|
176
|
+
case MODE_IZY: return `($${b}),Y`;
|
|
177
|
+
case MODE_REL: {
|
|
178
|
+
const offset = op1 < 128 ? op1 : op1 - 256;
|
|
179
|
+
const target = (pc + len + offset) & 0xFFFF;
|
|
180
|
+
return `$${this.hex4(target)}`;
|
|
181
|
+
}
|
|
182
|
+
case MODE_ZPI: return `($${b})`;
|
|
183
|
+
case MODE_AIX: return `($${w},X)`;
|
|
184
|
+
case MODE_ZPR: {
|
|
185
|
+
const offset = op2 < 128 ? op2 : op2 - 256;
|
|
186
|
+
const target = (pc + len + offset) & 0xFFFF;
|
|
187
|
+
return `$${b},$${this.hex4(target)}`;
|
|
188
|
+
}
|
|
189
|
+
default: return "";
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
renderVisibleRows() {
|
|
194
|
+
const container = this.contentElement.querySelector("#trace-scroll");
|
|
195
|
+
const spacer = this.contentElement.querySelector("#trace-spacer");
|
|
196
|
+
const rowsEl = this.contentElement.querySelector("#trace-rows");
|
|
197
|
+
if (!container || !spacer || !rowsEl) return;
|
|
198
|
+
if (!this.wasmModule._getTraceCount || !this.wasmModule._getTraceBuffer) return;
|
|
199
|
+
|
|
200
|
+
const count = this.wasmModule._getTraceCount();
|
|
201
|
+
const head = this.wasmModule._getTraceHead();
|
|
202
|
+
const capacity = this.wasmModule._getTraceCapacity();
|
|
203
|
+
const bufPtr = this.wasmModule._getTraceBuffer();
|
|
204
|
+
|
|
205
|
+
if (!bufPtr || count === 0) {
|
|
206
|
+
spacer.style.height = "0px";
|
|
207
|
+
rowsEl.innerHTML = '<div class="trace-empty">No trace data</div>';
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const totalHeight = count * this.ROW_HEIGHT;
|
|
212
|
+
spacer.style.height = totalHeight + "px";
|
|
213
|
+
|
|
214
|
+
const containerHeight = container.clientHeight;
|
|
215
|
+
const firstVisible = Math.floor(this.scrollTop / this.ROW_HEIGHT);
|
|
216
|
+
const visibleCount = Math.ceil(containerHeight / this.ROW_HEIGHT) + 1;
|
|
217
|
+
const startIdx = Math.max(0, firstVisible);
|
|
218
|
+
const endIdx = Math.min(count, startIdx + visibleCount);
|
|
219
|
+
|
|
220
|
+
// TraceEntry is 16 bytes (packed struct):
|
|
221
|
+
// uint16_t pc; uint8_t opcode, a, x, y, sp, p;
|
|
222
|
+
// uint8_t operand1, operand2, instrLen, padding;
|
|
223
|
+
// uint32_t cycle;
|
|
224
|
+
const ENTRY_SIZE = 16;
|
|
225
|
+
const heap = this.wasmModule.HEAPU8;
|
|
226
|
+
const modes = getAddrModeTable(this.wasmModule);
|
|
227
|
+
|
|
228
|
+
let html = "";
|
|
229
|
+
rowsEl.style.transform = `translateY(${startIdx * this.ROW_HEIGHT}px)`;
|
|
230
|
+
|
|
231
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
232
|
+
// Ring buffer index: oldest entry is at head (if full), newest is at head-1
|
|
233
|
+
let ringIdx;
|
|
234
|
+
if (count < capacity) {
|
|
235
|
+
ringIdx = i;
|
|
236
|
+
} else {
|
|
237
|
+
ringIdx = (head + i) % capacity;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const offset = bufPtr + ringIdx * ENTRY_SIZE;
|
|
241
|
+
const pc = heap[offset] | (heap[offset + 1] << 8);
|
|
242
|
+
const opcode = heap[offset + 2];
|
|
243
|
+
const a = heap[offset + 3];
|
|
244
|
+
const x = heap[offset + 4];
|
|
245
|
+
const y = heap[offset + 5];
|
|
246
|
+
const sp = heap[offset + 6];
|
|
247
|
+
const p = heap[offset + 7];
|
|
248
|
+
const op1 = heap[offset + 8];
|
|
249
|
+
const op2 = heap[offset + 9];
|
|
250
|
+
const len = heap[offset + 10];
|
|
251
|
+
const cycle = heap[offset + 12] | (heap[offset + 13] << 8) |
|
|
252
|
+
(heap[offset + 14] << 16) | (heap[offset + 15] << 24);
|
|
253
|
+
|
|
254
|
+
const mnemonic = getMnemonicTable(this.wasmModule)[opcode] || "???";
|
|
255
|
+
let bytesStr = this.hex2(opcode);
|
|
256
|
+
if (len >= 2) bytesStr += " " + this.hex2(op1);
|
|
257
|
+
if (len >= 3) bytesStr += " " + this.hex2(op2);
|
|
258
|
+
|
|
259
|
+
const mode = modes ? modes[opcode] : MODE_IMP;
|
|
260
|
+
const operand = this.formatOperand(mode, op1, op2, pc, len);
|
|
261
|
+
const flags = this.flagsStr(p);
|
|
262
|
+
|
|
263
|
+
html += `<div class="trace-row">` +
|
|
264
|
+
`<span class="trace-col-cycle">${(cycle >>> 0).toString()}</span>` +
|
|
265
|
+
`<span class="trace-col-pc">${this.hex4(pc)}</span>` +
|
|
266
|
+
`<span class="trace-col-bytes">${bytesStr.padEnd(8)}</span>` +
|
|
267
|
+
`<span class="trace-col-mnemonic">${mnemonic}</span>` +
|
|
268
|
+
`<span class="trace-col-operand">${operand}</span>` +
|
|
269
|
+
`<span class="trace-col-regs">${this.hex2(a)} ${this.hex2(x)} ${this.hex2(y)} ${this.hex2(sp)} ${flags}</span>` +
|
|
270
|
+
`</div>`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
rowsEl.innerHTML = html;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
hex2(v) { return v.toString(16).toUpperCase().padStart(2, "0"); }
|
|
277
|
+
hex4(v) { return v.toString(16).toUpperCase().padStart(4, "0"); }
|
|
278
|
+
|
|
279
|
+
flagsStr(p) {
|
|
280
|
+
return (
|
|
281
|
+
((p & 0x80) ? "N" : ".") +
|
|
282
|
+
((p & 0x40) ? "V" : ".") +
|
|
283
|
+
"-" +
|
|
284
|
+
((p & 0x10) ? "B" : ".") +
|
|
285
|
+
((p & 0x08) ? "D" : ".") +
|
|
286
|
+
((p & 0x04) ? "I" : ".") +
|
|
287
|
+
((p & 0x02) ? "Z" : ".") +
|
|
288
|
+
((p & 0x01) ? "C" : ".")
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* zero-page-watch-window.js - Zero page watch window for monitoring memory locations
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BaseWindow } from "../windows/base-window.js";
|
|
9
|
+
import { showToast } from "../ui/toast.js";
|
|
10
|
+
|
|
11
|
+
// Predefined watches for common Apple II zero page locations
|
|
12
|
+
const PREDEFINED_WATCHES = {
|
|
13
|
+
"BASIC Pointers": [
|
|
14
|
+
{ addr: 0x67, label: "TXTTAB", size: 16, desc: "Start of BASIC program" },
|
|
15
|
+
{ addr: 0x69, label: "VARTAB", size: 16, desc: "Start of variables" },
|
|
16
|
+
{ addr: 0x6b, label: "ARYTAB", size: 16, desc: "Start of arrays" },
|
|
17
|
+
{ addr: 0x6d, label: "STREND", size: 16, desc: "End of strings" },
|
|
18
|
+
{ addr: 0x6f, label: "FRETOP", size: 16, desc: "Top of string space" },
|
|
19
|
+
{ addr: 0x73, label: "MEMSIZ", size: 16, desc: "Top of memory" },
|
|
20
|
+
{ addr: 0x75, label: "CURLIN", size: 16, desc: "Current BASIC line" },
|
|
21
|
+
{ addr: 0xb8, label: "TXTPTR", size: 16, desc: "BASIC text pointer" },
|
|
22
|
+
],
|
|
23
|
+
"Screen/Window": [
|
|
24
|
+
{ addr: 0x20, label: "WNDLFT", size: 8, desc: "Window left edge" },
|
|
25
|
+
{ addr: 0x21, label: "WNDWDTH", size: 8, desc: "Window width" },
|
|
26
|
+
{ addr: 0x22, label: "WNDTOP", size: 8, desc: "Window top edge" },
|
|
27
|
+
{ addr: 0x23, label: "WNDBTM", size: 8, desc: "Window bottom" },
|
|
28
|
+
{ addr: 0x24, label: "CH", size: 8, desc: "Cursor horizontal" },
|
|
29
|
+
{ addr: 0x25, label: "CV", size: 8, desc: "Cursor vertical" },
|
|
30
|
+
{ addr: 0x28, label: "BASL", size: 16, desc: "Text base address" },
|
|
31
|
+
{ addr: 0x2a, label: "BAS2L", size: 16, desc: "Scroll line base" },
|
|
32
|
+
],
|
|
33
|
+
Graphics: [
|
|
34
|
+
{ addr: 0x26, label: "GBASL", size: 16, desc: "Graphics base address" },
|
|
35
|
+
{ addr: 0x30, label: "COLOR", size: 8, desc: "LoRes color" },
|
|
36
|
+
{ addr: 0xe0, label: "HCOLOR1", size: 8, desc: "HiRes color 1" },
|
|
37
|
+
{ addr: 0xe4, label: "HGRX", size: 16, desc: "HGR X coordinate" },
|
|
38
|
+
{ addr: 0xe6, label: "HGRY", size: 8, desc: "HGR Y coordinate" },
|
|
39
|
+
],
|
|
40
|
+
"DOS 3.3": [
|
|
41
|
+
{ addr: 0xaa, label: "DOSSLOT", size: 8, desc: "DOS slot" },
|
|
42
|
+
{ addr: 0xab, label: "DOSDRIVE", size: 8, desc: "DOS drive" },
|
|
43
|
+
{ addr: 0xac, label: "FILTYP", size: 8, desc: "File type" },
|
|
44
|
+
],
|
|
45
|
+
System: [
|
|
46
|
+
{ addr: 0x00, label: "LOC0", size: 16, desc: "General use" },
|
|
47
|
+
{ addr: 0x02, label: "LOC2", size: 16, desc: "General use" },
|
|
48
|
+
{ addr: 0x36, label: "CSWL", size: 16, desc: "Char output hook" },
|
|
49
|
+
{ addr: 0x38, label: "KSWL", size: 16, desc: "Char input hook" },
|
|
50
|
+
{ addr: 0x3c, label: "A1L", size: 16, desc: "Monitor A1" },
|
|
51
|
+
{ addr: 0x3e, label: "A2L", size: 16, desc: "Monitor A2" },
|
|
52
|
+
{ addr: 0x42, label: "A4L", size: 16, desc: "Monitor A4" },
|
|
53
|
+
{ addr: 0x45, label: "ACC", size: 8, desc: "Accumulator save" },
|
|
54
|
+
{ addr: 0x46, label: "XREG", size: 8, desc: "X register save" },
|
|
55
|
+
{ addr: 0x47, label: "YREG", size: 8, desc: "Y register save" },
|
|
56
|
+
{ addr: 0x48, label: "STATUS", size: 8, desc: "Status save" },
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export class ZeroPageWatchWindow extends BaseWindow {
|
|
61
|
+
constructor(wasmModule) {
|
|
62
|
+
super({
|
|
63
|
+
id: "zeropage-watch",
|
|
64
|
+
title: "Zero Page Watch",
|
|
65
|
+
defaultWidth: 400,
|
|
66
|
+
defaultHeight: 450,
|
|
67
|
+
minWidth: 400,
|
|
68
|
+
minHeight: 300,
|
|
69
|
+
maxWidth: 400,
|
|
70
|
+
});
|
|
71
|
+
this.wasmModule = wasmModule;
|
|
72
|
+
this.customWatches = this.loadCustomWatches();
|
|
73
|
+
this.previousValues = new Map();
|
|
74
|
+
this.changedAddresses = new Set();
|
|
75
|
+
this.changeTimestamps = new Map();
|
|
76
|
+
this.expandedGroups = new Set(["BASIC Pointers", "Screen/Window"]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
loadCustomWatches() {
|
|
80
|
+
try {
|
|
81
|
+
const saved = localStorage.getItem("zp-custom-watches");
|
|
82
|
+
return saved ? JSON.parse(saved) : [];
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.warn("Failed to load custom watches:", e.message);
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
saveCustomWatches() {
|
|
90
|
+
try {
|
|
91
|
+
localStorage.setItem(
|
|
92
|
+
"zp-custom-watches",
|
|
93
|
+
JSON.stringify(this.customWatches),
|
|
94
|
+
);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.warn("Failed to save custom watches:", e.message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getState() {
|
|
101
|
+
const base = super.getState();
|
|
102
|
+
base.expandedGroups = [...this.expandedGroups];
|
|
103
|
+
return base;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
restoreState(state) {
|
|
107
|
+
if (state.expandedGroups) {
|
|
108
|
+
this.expandedGroups = new Set(state.expandedGroups);
|
|
109
|
+
}
|
|
110
|
+
super.restoreState(state);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
renderContent() {
|
|
114
|
+
return `
|
|
115
|
+
<div class="zp-toolbar">
|
|
116
|
+
<button class="zp-add-btn" title="Add custom watch">+ Add Watch</button>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="zp-groups"></div>
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
onContentRendered() {
|
|
123
|
+
this.groupsDiv = this.contentElement.querySelector(".zp-groups");
|
|
124
|
+
|
|
125
|
+
this.contentElement
|
|
126
|
+
.querySelector(".zp-add-btn")
|
|
127
|
+
.addEventListener("click", () => {
|
|
128
|
+
this.addCustomWatch();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
this.renderGroups();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
renderGroups() {
|
|
135
|
+
let html = "";
|
|
136
|
+
|
|
137
|
+
// Render predefined groups
|
|
138
|
+
for (const [groupName, watches] of Object.entries(PREDEFINED_WATCHES)) {
|
|
139
|
+
const isExpanded = this.expandedGroups.has(groupName);
|
|
140
|
+
html += `
|
|
141
|
+
<div class="zp-group">
|
|
142
|
+
<div class="zp-group-header" data-group="${groupName}">
|
|
143
|
+
<span class="zp-expand-icon">${isExpanded ? "▼" : "▶"}</span>
|
|
144
|
+
<span class="zp-group-name">${groupName}</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="zp-group-content ${isExpanded ? "" : "collapsed"}">
|
|
147
|
+
${watches.map((w) => this.renderWatch(w)).join("")}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Render custom watches group if any exist
|
|
154
|
+
if (this.customWatches.length > 0) {
|
|
155
|
+
const isExpanded = this.expandedGroups.has("Custom");
|
|
156
|
+
html += `
|
|
157
|
+
<div class="zp-group">
|
|
158
|
+
<div class="zp-group-header" data-group="Custom">
|
|
159
|
+
<span class="zp-expand-icon">${isExpanded ? "▼" : "▶"}</span>
|
|
160
|
+
<span class="zp-group-name">Custom Watches</span>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="zp-group-content ${isExpanded ? "" : "collapsed"}">
|
|
163
|
+
${this.customWatches.map((w, i) => this.renderWatch(w, i)).join("")}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.groupsDiv.innerHTML = html;
|
|
170
|
+
|
|
171
|
+
// Add event listeners for group headers
|
|
172
|
+
this.groupsDiv.querySelectorAll(".zp-group-header").forEach((header) => {
|
|
173
|
+
header.addEventListener("click", () => {
|
|
174
|
+
const groupName = header.dataset.group;
|
|
175
|
+
if (this.expandedGroups.has(groupName)) {
|
|
176
|
+
this.expandedGroups.delete(groupName);
|
|
177
|
+
} else {
|
|
178
|
+
this.expandedGroups.add(groupName);
|
|
179
|
+
}
|
|
180
|
+
this.renderGroups();
|
|
181
|
+
if (this.onStateChange) this.onStateChange();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Add event listeners for removing custom watches
|
|
186
|
+
this.groupsDiv.querySelectorAll(".zp-remove-btn").forEach((btn) => {
|
|
187
|
+
btn.addEventListener("click", (e) => {
|
|
188
|
+
e.stopPropagation();
|
|
189
|
+
const index = parseInt(btn.dataset.index, 10);
|
|
190
|
+
this.customWatches.splice(index, 1);
|
|
191
|
+
this.saveCustomWatches();
|
|
192
|
+
this.renderGroups();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
renderWatch(watch, customIndex = null) {
|
|
198
|
+
const isCustom = customIndex !== null;
|
|
199
|
+
const sizeIndicator = watch.size === 16 ? "16" : "8";
|
|
200
|
+
return `
|
|
201
|
+
<div class="zp-watch" data-addr="${watch.addr}" data-size="${watch.size}">
|
|
202
|
+
<span class="zp-addr">$${this.formatHex(watch.addr, 2)}</span>
|
|
203
|
+
<span class="zp-label">${watch.label}</span>
|
|
204
|
+
<span class="zp-size">${sizeIndicator}b</span>
|
|
205
|
+
<span class="zp-value" data-addr="${watch.addr}" data-size="${watch.size}">----</span>
|
|
206
|
+
<span class="zp-desc">${watch.desc || ""}</span>
|
|
207
|
+
${isCustom ? `<button class="zp-remove-btn" data-index="${customIndex}" title="Remove">×</button>` : ""}
|
|
208
|
+
</div>
|
|
209
|
+
`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
addCustomWatch() {
|
|
213
|
+
const addrStr = prompt("Enter zero page address (hex, 00-FF):", "00");
|
|
214
|
+
if (!addrStr) return;
|
|
215
|
+
|
|
216
|
+
const addr = parseInt(addrStr, 16);
|
|
217
|
+
if (isNaN(addr) || addr < 0 || addr > 0xff) {
|
|
218
|
+
showToast("Invalid address. Must be 00-FF.", "error");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const label = prompt(
|
|
223
|
+
"Enter label for this watch:",
|
|
224
|
+
`$${this.formatHex(addr, 2)}`,
|
|
225
|
+
);
|
|
226
|
+
if (!label) return;
|
|
227
|
+
|
|
228
|
+
const sizeStr = prompt("Size (8 or 16 bits):", "8");
|
|
229
|
+
const size = sizeStr === "16" ? 16 : 8;
|
|
230
|
+
|
|
231
|
+
this.customWatches.push({
|
|
232
|
+
addr,
|
|
233
|
+
label,
|
|
234
|
+
size,
|
|
235
|
+
desc: "Custom watch",
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
this.expandedGroups.add("Custom");
|
|
239
|
+
this.saveCustomWatches();
|
|
240
|
+
this.renderGroups();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
update(wasmModule) {
|
|
244
|
+
if (!this.isVisible || !this.groupsDiv) return;
|
|
245
|
+
|
|
246
|
+
const now = Date.now();
|
|
247
|
+
const fadeTime = 1000;
|
|
248
|
+
|
|
249
|
+
// Update all value displays
|
|
250
|
+
this.groupsDiv.querySelectorAll(".zp-value").forEach((valueSpan) => {
|
|
251
|
+
const addr = parseInt(valueSpan.dataset.addr, 10);
|
|
252
|
+
const size = parseInt(valueSpan.dataset.size, 10);
|
|
253
|
+
|
|
254
|
+
let value;
|
|
255
|
+
if (size === 16) {
|
|
256
|
+
const low = wasmModule._peekMemory(addr);
|
|
257
|
+
const high = wasmModule._peekMemory((addr + 1) & 0xff);
|
|
258
|
+
value = (high << 8) | low;
|
|
259
|
+
} else {
|
|
260
|
+
value = wasmModule._peekMemory(addr);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const prevValue = this.previousValues.get(addr);
|
|
264
|
+
|
|
265
|
+
// Detect changes
|
|
266
|
+
if (prevValue !== undefined && prevValue !== value) {
|
|
267
|
+
this.changeTimestamps.set(addr, now);
|
|
268
|
+
}
|
|
269
|
+
this.previousValues.set(addr, value);
|
|
270
|
+
|
|
271
|
+
// Format value
|
|
272
|
+
const digits = size === 16 ? 4 : 2;
|
|
273
|
+
let displayValue = `$${this.formatHex(value, digits)}`;
|
|
274
|
+
|
|
275
|
+
// Add decimal value for small numbers
|
|
276
|
+
if (value < 256) {
|
|
277
|
+
displayValue += ` (${value})`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
valueSpan.textContent = displayValue;
|
|
281
|
+
|
|
282
|
+
// Highlight recent changes
|
|
283
|
+
const watchDiv = valueSpan.closest(".zp-watch");
|
|
284
|
+
if (this.changeTimestamps.has(addr)) {
|
|
285
|
+
const elapsed = now - this.changeTimestamps.get(addr);
|
|
286
|
+
if (elapsed < fadeTime) {
|
|
287
|
+
watchDiv.classList.add("changed");
|
|
288
|
+
} else {
|
|
289
|
+
watchDiv.classList.remove("changed");
|
|
290
|
+
this.changeTimestamps.delete(addr);
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
watchDiv.classList.remove("changed");
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|