| 1 |
# X16Emu ADD — the Advanced Disassembler and Debugger for the Commander X16 |
| 2 |
|
| 3 |
<img src="./.gh/logo.png" align="right" width="200" alt="X16Emu ADD logo" /> |
| 4 |
|
| 5 |
[](https://github.com/xylothan/x16-emulator/actions/workflows/build.yml) |
| 6 |
[](https://github.com/xylothan/x16-emulator/releases) |
| 7 |
[](https://github.com/xylothan/x16-emulator/releases/tag/dev) |
| 8 |
[](./LICENSE) |
| 9 |
[](https://github.com/xylothan/x16-emulator/graphs/contributors) |
| 10 |
|
| 11 |
|
| 12 |
**X16Emu ADD** — the **A**dvanced **D**isassembler and **D**ebugger — is a fork of the |
| 13 |
[official Commander X16 emulator][upstream] built for people who write software for the X16 |
| 14 |
rather than just run it. It is the upstream emulator with a real debugger bolted on: a dockable |
| 15 |
graphical debug UI, source-level debugging from cc65 `.dbg` files, and a Debug Adapter Protocol |
| 16 |
server so the machine can be driven from VS Code, Visual Studio, or any other DAP-speaking editor. |
| 17 |
|
| 18 |
Everything the official emulator does, ADD does too. |
| 19 |
|
| 20 |
> ### ⚠️ This is experimental software |
| 21 |
> |
| 22 |
> The emulator core is upstream and is great, but **the debugger, the DAP server and the |
| 23 |
> Windows-specific changes are experimental** and probably not great. Expect rough edges, and |
| 24 |
> expect details to change between releases. |
| 25 |
> |
| 26 |
> That said it has been used successfully to carry several real Commodore 64 to X16 porting |
| 27 |
> projects through to completion, which is exactly the work it was built for. Anyone debugging |
| 28 |
> assembly on the X16 today will very likely save time with it. This software is purpose built |
| 29 |
> to further personal projects and make my life easier. If it makes your life easier, great! |
| 30 |
> |
| 31 |
> **It is published as-is, and is not formally supported at this time.** |
| 32 |
|
| 33 |
Why X16Emu ADD |
| 34 |
--------------- |
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
| **Graphical debugger** | A dockable Dear ImGui debug window with disassembly, CPU, memory, source, call stack, symbols, breakpoints, I/O and file access, VERA/PSG/FM/PCM inspectors, and a performance budget view. | |
| 39 |
| **Source-level debugging** | Point it at a cc65 `.dbg` file and step through the original `.s`/`.c` source, with labels, equates and live values. `.dbg` files auto-load, including for overlays the program `LOAD`s at runtime. | |
| 40 |
| **Debug from an editor** | A built-in DAP server means breakpoints, stepping, watches, memory and disassembly in VS Code, Visual Studio, or any DAP-speaking client. | |
| 41 |
| **Conditional breakpoints** | Break on `A == $05`, `byte[$1234] != 0`, a specific RAM bank, or the Nth hit. | |
| 42 |
| **Watchpoints** | Break on writes to an address or a whole range, with up to 64 of them. | |
| 43 |
| **Scriptable input** | Inject keystrokes, typed text and joystick state over DAP, making automated regression runs of a game practical. | |
| 44 |
| **VERA inspectors** | Live palette, tile, sprite, bitmap and tilemap viewers plus fully decoded VERA registers — decoded per scanline, so raster splits are visible. | |
| 45 |
| **65C816 / GS aware** | 24-bit bank:address memory browsing, mode-correct register widths, and the X16 virtual registers R0–R15 broken out. | |
| 46 |
| **Real Windows builds** | Self-contained statically linked `x16emu.exe` for x64, x86 and ARM64. No SDL2, zlib or Visual C++ redistributable to install. | |
| 47 |
| **The window doesn't freeze** | On Windows the emulator keeps running and painting while its window is dragged or resized. | |
| 48 |
| **Automated releases** | Every push is built for all platforms; tagging stages a draft release with all the assets attached, ready for a human to publish. | |
| 49 |
|
| 50 |
[](./.gh/screenshots/debugger-overview.png) |
| 51 |
|
| 52 |
*Stopped mid-loop in a cc65 assembly program: your own source on the left with the current line |
| 53 |
highlighted, 65C816-aware registers and the X16 virtual registers `R0`–`R15` on the right, and a |
| 54 |
live hex editor underneath. Every panel is dockable, and the machine keeps running in its own |
| 55 |
window while you inspect it.* |
| 56 |
|
| 57 |
The debugger is documented in full, panel by panel and with a screenshot of each, under |
| 58 |
[Advanced Debugging](#advanced-debugging) below. |
| 59 |
|
| 60 |
Relationship to the official emulator |
| 61 |
------------------------------------- |
| 62 |
|
| 63 |
ADD exists to add debugging tooling, not to diverge from the X16 platform. It has no relation |
| 64 |
to upstream development. Don't bug the upstream developers about it. |
| 65 |
|
| 66 |
**Everything about the Commander X16 itself — the KERNAL, BASIC, VERA, the hardware, the file |
| 67 |
formats — is documented officially, and those docs apply here unchanged:** |
| 68 |
|
| 69 |
* [Official X16 documentation][x16docs] — the reference for the machine, its KERNAL and BASIC |
| 70 |
* [x16-rom][x16rom] — the KERNAL/BASIC ROM sources |
| 71 |
* [Official emulator][upstream] — upstream, whose release notes cover the emulator core each ADD build is based on |
| 72 |
* [commanderx16.com][website] |
| 73 |
|
| 74 |
Use the official docs for the machine. Use this README for what ADD adds on top. |
| 75 |
|
| 76 |
### Version and release numbering |
| 77 |
|
| 78 |
Releases are named **`R49.nnn`**: |
| 79 |
|
| 80 |
* `R49` names the official Commander X16 release line the build follows. It is not a promise of |
| 81 |
byte-for-byte equivalence with official R49: the emulator core tracks upstream's ongoing |
| 82 |
development after that release, so a build may include upstream fixes made since R49 was cut. |
| 83 |
* `nnn` counts the ADD builds on top of it, and goes up as ADD's own features land. |
| 84 |
|
| 85 |
When upstream cuts its next release, ADD releases move to `R50.001` and so on. A release is cut by |
| 86 |
pushing an `R49.nnn` **tag**, which is what the build workflow keys on; if you also want a branch |
| 87 |
for a release line, give it a distinct name such as `release/R49.001`, since a branch and a tag |
| 88 |
sharing one name makes `R49.001` ambiguous to git. |
| 89 |
|
| 90 |
Each release package ships the `rom.bin` it was built and tested against — use that one. Older ROMs |
| 91 |
may not work with newer emulators, and vice versa. |
| 92 |
|
| 93 |
Features |
| 94 |
-------- |
| 95 |
|
| 96 |
Inherited from the official emulator: |
| 97 |
|
| 98 |
* CPU: 65C02 and 65C816 instruction sets, selected by command line switch |
| 99 |
* VERA |
| 100 |
- Mostly cycle exact emulation |
| 101 |
- Supports almost all features: |
| 102 |
- composer |
| 103 |
- two layers |
| 104 |
- sprites |
| 105 |
- VSYNC, raster, sprite IRQ |
| 106 |
* Sound |
| 107 |
- PCM |
| 108 |
- PSG |
| 109 |
- YM2151 |
| 110 |
- MIDI via FluidSynth |
| 111 |
* Real-Time-Clock |
| 112 |
* NVRAM |
| 113 |
* System Management Controller |
| 114 |
* SD card: reading and writing (image file) |
| 115 |
* VIA |
| 116 |
- ROM/RAM banking |
| 117 |
- keyboard |
| 118 |
- mouse |
| 119 |
- gamepads |
| 120 |
|
| 121 |
Added by ADD: |
| 122 |
|
| 123 |
* Dear ImGui graphical debugger (`-imgui`) |
| 124 |
* Debug Adapter Protocol server for editor-based debugging (`-debugport`) |
| 125 |
* cc65 source-level debugging (`-dbgfile`, `-srcpath`) |
| 126 |
* Conditional breakpoints, hit counts and memory watchpoints |
| 127 |
* Statically linked MSVC builds for Windows x64/x86/ARM64 |
| 128 |
* Non-blocking window drag and resize on Windows |
| 129 |
|
| 130 |
Binaries & Compiling |
| 131 |
-------------------- |
| 132 |
|
| 133 |
Binary releases for macOS, Windows and Linux are available on the [X16Emu ADD releases page][releases]. |
| 134 |
The official builds, which have upstream's keyboard-driven debugger but none of ADD's tooling, are |
| 135 |
[over here][upstream-releases]. |
| 136 |
|
| 137 |
### Which Windows download? |
| 138 |
|
| 139 |
Windows ships in three flavours. Unless you have a reason to pick otherwise, take `x16emu_win64`. |
| 140 |
|
| 141 |
|
| 142 |
|
| 143 |
| `x16emu_win64`, `x16emu_win32`, `x16emu_win-arm64` | A single self-contained `x16emu.exe`, statically linked. Nothing to install — no SDL2, zlib or Visual C++ redistributable. Built without FluidSynth, so it offers no MIDI options at all. | |
| 144 |
| `…-midi` | A separate build with FluidSynth compiled in, shipped with `libfluidsynth-3.dll` for the MIDI synth (`-midicard` / `-sf2`). Take this if you want MIDI. | |
| 145 |
| `x16emu_win64-mingw`, `x16emu_win32-mingw` | The older MinGW build, kept as a fallback. Ships MIDI support and the `-trace` option, at the cost of around 25 DLLs alongside the executable. | |
| 146 |
|
| 147 |
All of them include the debugger and the DAP server. |
| 148 |
|
| 149 |
### Development builds |
| 150 |
|
| 151 |
Every check-in to `main` refreshes the [`dev` prerelease][dev-release], so there is always a |
| 152 |
current build to download without waiting for a tagged release. It is not an official release and |
| 153 |
carries no promises, but it is built and unit-tested by the same workflow that cuts the real ones. |
| 154 |
|
| 155 |
The tag and the file names never change, so a link keeps working as the build rolls forward: |
| 156 |
|
| 157 |
``` |
| 158 |
https://github.com/xylothan/x16-emulator/releases/download/dev/x16emu_win64-dev.zip |
| 159 |
https://github.com/xylothan/x16-emulator/releases/download/dev/x16emu_linux-x86_64-dev.zip |
| 160 |
https://github.com/xylothan/x16-emulator/releases/download/dev/x16emu_macos_m1-dev.zip |
| 161 |
``` |
| 162 |
|
| 163 |
Swap in any package name from the table above. Which build you actually have is recorded three |
| 164 |
ways: in the release title, in `BUILD_VERSION.txt` inside the zip, and on the last page of the |
| 165 |
bundled `README.pdf`. They all read the same identifier, for example: |
| 166 |
|
| 167 |
``` |
| 168 |
r49-dev-20260810.003 |
| 169 |
``` |
| 170 |
|
| 171 |
That is the upstream release this fork sits on, the UTC date, and how many commits landed that |
| 172 |
day — so `.003` is the third check-in of the tenth. |
| 173 |
|
| 174 |
Two differences worth knowing about the default builds: |
| 175 |
|
| 176 |
- The non-`-midi` packages are built without FluidSynth, so they do not offer the MIDI options |
| 177 |
at all — they will not appear in `-h`, and dropping the DLL next to one will not enable them. |
| 178 |
- The MSVC builds have no `-trace` option. The generated ROM listing contains a string literal |
| 179 |
larger than MSVC's 16 KB limit, so a trace-enabled build cannot compile. Use the `-mingw` |
| 180 |
package if you need `-trace`. |
| 181 |
|
| 182 |
The emulator itself is dependent only on SDL2. However, to run the emulated system you will also need a compatible `rom.bin` ROM image. This will be |
| 183 |
loaded from the directory containing the emulator binary, or you can use the `-rom .../path/to/rom.bin` option. |
| 184 |
|
| 185 |
> **WARNING:** Older versions of the ROM might not work in newer versions of the emulator, and vice versa. |
| 186 |
|
| 187 |
You can build a ROM image yourself using the [build instructions][x16rom-build] in the [x16-rom][x16rom] repo. The `rom.bin` included in the [*latest* release][releases] of the emulator may also work with the HEAD of this repo, but this is not guaranteed. |
| 188 |
|
| 189 |
### Building from source |
| 190 |
|
| 191 |
The emulator depends on SDL2. The debugger and DAP server add two more requirements: |
| 192 |
|
| 193 |
* a **C++17** compiler, because Dear ImGui and the debug UI are written in C++ (Dear ImGui itself is |
| 194 |
vendored in `src/extern/imgui/`, so there is nothing to install for it), and |
| 195 |
* **cJSON**, used by the DAP server — `libcjson-dev` on Debian/Ubuntu, `cjson` on Homebrew, |
| 196 |
or supplied by vcpkg on Windows. |
| 197 |
|
| 198 |
Both the ImGui debugger and the DAP server are always compiled in; `-imgui` and `-debugport` |
| 199 |
are runtime switches, so there is nothing to enable at build time. |
| 200 |
|
| 201 |
### macOS Build |
| 202 |
|
| 203 |
Install SDL2 using `brew install sdl2`, and cJSON using `brew install cjson`. |
| 204 |
|
| 205 |
### Linux Build |
| 206 |
|
| 207 |
The SDL2 development package is available from the package repositories of most major Linux distributions: |
| 208 |
- Red Hat: `yum install SDL2-devel cjson-devel` |
| 209 |
- Debian: `apt-get install libsdl2-dev libcjson-dev` |
| 210 |
|
| 211 |
Type `make` to build the source. The output will be `x16emu` in the current directory. Remember you will also need a `rom.bin` as described above. |
| 212 |
|
| 213 |
### WebAssembly Build |
| 214 |
|
| 215 |
Steps for compiling WebAssembly/HTML5 can be found [here][webassembly]. |
| 216 |
|
| 217 |
### Windows Build |
| 218 |
|
| 219 |
`build.ps1` at the repo root wraps all of this. It finds the installed Visual Studio, picks the |
| 220 |
CMake that can actually drive that generator, locates vcpkg, and selects the matching preset from |
| 221 |
`CMakePresets.json`: |
| 222 |
|
| 223 |
```powershell |
| 224 |
.\build.ps1 # Debug build of the emulator |
| 225 |
.\build.ps1 -Config Release # Release build |
| 226 |
.\build.ps1 -Tests # build and run the unit test suite |
| 227 |
.\build.ps1 -Run # build, then launch it |
| 228 |
.\build.ps1 -DapTest -FetchRom # download a ROM, boot, run the DAP testbench |
| 229 |
.\build.ps1 -Dbg # build tools/x16dbg, the .NET DAP client |
| 230 |
``` |
| 231 |
|
| 232 |
Useful extras: `-Target <name>` for a single CMake target, `-Rom <path>` to point at a ROM (or set |
| 233 |
`X16_ROM`), `-Clean` / `-Reconfigure`, `-Force` to stop an emulator that is holding `x16emu.exe` |
| 234 |
open, and `-EmuArgs '-prg','A.PRG','-run'` to pass arguments through to the emulator. |
| 235 |
`Get-Help .\build.ps1 -Full` lists them all. |
| 236 |
|
| 237 |
Two things it exists to save you from: the CMake on `PATH` is often older than the installed |
| 238 |
Visual Studio and does not know its generator, which fails with a wall of generator names that |
| 239 |
never mentions the real problem; and a running emulator holds its own `.exe` open, so the link step |
| 240 |
dies with a bare `LNK1104` that says nothing about why. |
| 241 |
|
| 242 |
#### Building by hand |
| 243 |
|
| 244 |
The Windows releases are built with **MSVC and CMake**, using vcpkg for dependencies and custom |
| 245 |
triplets in `vcpkg-triplets/` that link everything statically. That is what produces the |
| 246 |
self-contained `x16emu.exe` in the release packages: |
| 247 |
|
| 248 |
``` |
| 249 |
cmake -B build -G "Visual Studio 17 2022" -A x64 ^ |
| 250 |
-DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALLATION_ROOT%/scripts/buildsystems/vcpkg.cmake ^ |
| 251 |
-DVCPKG_OVERLAY_TRIPLETS=vcpkg-triplets ^ |
| 252 |
-DVCPKG_TARGET_TRIPLET=x64-windows-x16emu ^ |
| 253 |
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded ^ |
| 254 |
-DENABLE_TRACE=OFF |
| 255 |
cmake --build build --config Release |
| 256 |
``` |
| 257 |
|
| 258 |
Use `-A Win32` with `x86-windows-x16emu`, or `-A ARM64` with `arm64-windows-x16emu`, for the other |
| 259 |
architectures. Add `-DENABLE_FLUIDSYNTH=ON -DVCPKG_MANIFEST_FEATURES=midi` for a MIDI build. |
| 260 |
`ENABLE_TRACE` must stay off under MSVC: the generated ROM listing contains a string literal |
| 261 |
larger than MSVC's 16 KB limit. |
| 262 |
|
| 263 |
The output is `build\Release\x16emu.exe` and `build\Release\makecart.exe`. Remember you will also |
| 264 |
need a `rom.bin` as described above. |
| 265 |
|
| 266 |
Cross-compiling from macOS/Linux/MSYS2 with mingw-w64 also still works. Install the mingw-w64 |
| 267 |
toolchain and the mingw32 version of SDL, then: |
| 268 |
``` |
| 269 |
CROSS_COMPILE_WINDOWS=1 MINGW32=/usr/x86_64-w64-mingw32 WIN_SDL2=/usr/x86_64-w64-mingw32 make |
| 270 |
``` |
| 271 |
Paths to those libraries can be changed to your installation directory if they aren't located there. |
| 272 |
|
| 273 |
The output will be `x16emu.exe` in the current directory. You will also need `SDL2.dll` from SDL2's binary folder. |
| 274 |
|
| 275 |
|
| 276 |
Starting |
| 277 |
-------- |
| 278 |
|
| 279 |
You can start `x16emu`/`x16emu.exe` either by double-clicking it, or from the command line. The |
| 280 |
latter allows you to specify additional arguments. When starting `x16emu` without arguments, it |
| 281 |
will pick up the system ROM (`rom.bin`) from the executable's directory. |
| 282 |
|
| 283 |
The command line options of the official emulator work here too — `-prg`, `-bas`, `-run`, `-scale`, |
| 284 |
`-sdcard`, `-fsroot`, `-warp`, `-gif`, `-wav`, `-ram`, `-c816` and the rest. They are documented in |
| 285 |
the [official emulator's README][upstream-readme]. Two caveats, both from the packaging rather than |
| 286 |
from ADD itself: the MSVC builds have no `-trace`, and the non-`-midi` packages have no MIDI |
| 287 |
options at all. `x16emu -h` is the authoritative list for the build you actually have. |
| 288 |
|
| 289 |
The options below are the ones ADD adds on top. They all concern debugging. |
| 290 |
|
| 291 |
|
| 292 |
|
| 293 |
| `-imgui` | Opens the graphical debugger in its own window, with dockable panels for the CPU, memory, disassembly, source, breakpoints, symbols, the call stack, VERA graphics, the three audio sources, I/O ports and file access, and the performance budget. Additive: independent of, and combinable with, `-debug`. | |
| 294 |
| `-debugport [<port>]` | Starts the Debug Adapter Protocol server so an IDE can attach. Default port 9009. | |
| 295 |
| `-bp <address>` | Arms a breakpoint before the first instruction runs, for catching start-up code you could never attach to in time. Can be repeated. Needs a debugger to resume from, so it opens `-debug` unless `-imgui` or `-debugport` is already giving you one. See [Catching early boot with `-bp`](#catching-early-boot-with--bp). | |
| 296 |
| `-dbgfile <path>` | Loads a cc65 `.dbg` file, so addresses map back to source files and line numbers. | |
| 297 |
| `-dbgauto` / `-no-dbgauto` | Whether to merge the `.dbg` beside each program the machine loads. Follows the debugger unless forced either way. | |
| 298 |
| `-srcpath <dir>` | Adds a directory to search for the source files a `.dbg` names. Can be repeated. | |
| 299 |
|
| 300 |
Upstream's `-debug [<address>]` still works. ADD does not extend it; see the |
| 301 |
[official README][upstream-readme] for what it does. |
| 302 |
|
| 303 |
All of these are covered properly under [Advanced Debugging](#advanced-debugging). |
| 304 |
|
| 305 |
Using the emulated machine |
| 306 |
-------------------------- |
| 307 |
|
| 308 |
Running the X16 itself — the keyboard layouts, BASIC and the screen editor, SD card images, the |
| 309 |
host filesystem interface, GIF and WAV recording, the emulator I/O registers at `$9FB0`-`$9FBF`, |
| 310 |
the CRT cartridge format, the `makecart` tool, and the WebEmulator's URL options — is identical |
| 311 |
to the official emulator and is documented there, with one addition: ADD uses the otherwise unused |
| 312 |
register **`$9FBC`** for [performance budget markers](docs/perf-budget.md), which a program can |
| 313 |
write to say exactly where its frame work begins and ends. It reads back a protocol version, so |
| 314 |
guest code can detect whether the emulator it is running under understands them. |
| 315 |
|
| 316 |
* [Official emulator README][upstream-readme] — command line options, recording, I/O registers, |
| 317 |
SD card images, HostFS, the CRT format and `makecart` |
| 318 |
* [Official X16 documentation][x16docs] — the machine itself, its KERNAL and BASIC |
| 319 |
* [WebAssembly notes][webassembly] — building the web target |
| 320 |
|
| 321 |
The emulator hotkeys (`Ctrl`+`F` for fullscreen, `Ctrl`+`M` for mouse capture, `Ctrl`+`R` to |
| 322 |
reset, `Ctrl`+`V` to paste, and so on, or their command-key equivalents on macOS) are upstream's |
| 323 |
and are unchanged. ADD adds a few of its own, listed under |
| 324 |
[Keyboard shortcuts](#keyboard-shortcuts). |
| 325 |
|
| 326 |
|
| 327 |
Advanced Debugging |
| 328 |
------------------ |
| 329 |
|
| 330 |
This is the part of the emulator ADD exists for. There are two ways to debug, and they are |
| 331 |
additive — either or both can be turned on: |
| 332 |
|
| 333 |
|
| 334 |
|
| 335 |
| A graphical debugger with panels, source view and VERA inspectors | **ImGui debugger** | `-imgui` | |
| 336 |
| To debug from VS Code / Visual Studio / a script | **DAP server** | `-debugport` | |
| 337 |
|
| 338 |
Upstream's own small keyboard-driven debugger (`-debug`) is still present and still works, but |
| 339 |
ADD does not extend it and it is documented [upstream][upstream-readme]. Everything below |
| 340 |
describes the ImGui debugger and the DAP server. |
| 341 |
|
| 342 |
A good default for day-to-day assembly work: |
| 343 |
|
| 344 |
``` |
| 345 |
x16emu -imgui -prg myprog.prg -run |
| 346 |
``` |
| 347 |
|
| 348 |
A good default when your source is built with cc65: |
| 349 |
|
| 350 |
``` |
| 351 |
x16emu -imgui -prg myprog.prg -run -dbgfile myprog.dbg -srcpath ./src |
| 352 |
``` |
| 353 |
|
| 354 |
> ⚠️ The ImGui debugger and the DAP server are **experimental and probably broken**. They have |
| 355 |
> helped complete several real C64→X16 porting projects, but treat the debugger like a goat that |
| 356 |
> eats your homework: if something looks funny, assume it is doing something wrong until you can |
| 357 |
> prove otherwise. Expect rough edges and occasional changes to keys, layouts and DAP details |
| 358 |
> between releases. |
| 359 |
|
| 360 |
#### Using the debugger and an editor at the same time |
| 361 |
|
| 362 |
Breakpoints live in one place. Whether a breakpoint came from `-bp` on the command line, F9 in |
| 363 |
the debugger, the ImGui breakpoints panel, or your editor over DAP, there is a single table |
| 364 |
deciding where the machine stops, and each of those can hold a breakpoint at the same address |
| 365 |
without disturbing the others. Disconnecting your editor takes away the breakpoints *it* asked |
| 366 |
for and leaves yours alone. |
| 367 |
|
| 368 |
The emulator's own UIs share one view of that table: a breakpoint you set with F9 appears in the |
| 369 |
ImGui panel, and one you disable there still shows in the Disassembly and Source gutters. |
| 370 |
|
| 371 |
An editor attached over DAP is the exception, because it keeps its own list: |
| 372 |
|
| 373 |
* A breakpoint you set with F9 will not appear in your editor's gutter. |
| 374 |
* A breakpoint you delete with F9 goes away in the emulator, but your editor still believes in |
| 375 |
it — and because DAP clients re-send a source file's whole breakpoint list whenever you edit |
| 376 |
it, your editor will usually put it back. |
| 377 |
|
| 378 |
This is a limitation of the protocol rather than an oversight: in DAP the client owns its |
| 379 |
breakpoint list and is expected to be authoritative about it. If you are driving the emulator |
| 380 |
from an editor, the least surprising approach is to manage breakpoints from the editor and treat |
| 381 |
F9 as a local, temporary override. |
| 382 |
|
| 383 |
### The ImGui debugger |
| 384 |
|
| 385 |
`-imgui` opens a second, resizable OS window titled **"Commander X16 - ImGui Debugger"**, |
| 386 |
960×720, placed next to the emulator window. It is available in the native Windows, Linux and |
| 387 |
macOS builds. |
| 388 |
|
| 389 |
It does not require `-debug`, and it does not replace the emulator window — the machine keeps |
| 390 |
running in its own window while you inspect it. |
| 391 |
|
| 392 |
#### Panels |
| 393 |
|
| 394 |
Every panel is dockable, closable and reopenable from the **View** menu. Each one is shown in |
| 395 |
[A tour of the panels](#a-tour-of-the-panels) below. |
| 396 |
|
| 397 |
|
| 398 |
|
| 399 |
| [**Disassembly**](#disassembly) | Live disassembly around the PC. Hovering an operand shows the effective address and the value there. Right-click for *Run to here* and *Toggle breakpoint*. | |
| 400 |
| [**CPU**](#cpu-virtual-registers-and-the-stack) | Registers with 65C816-aware widths, decoded status flags, and the stack with the most recent push on top. A collapsible **Virtual Regs (R0–R15)** section shows the X16 pseudo-registers at `$02`–`$21`. A collapsible **Watch** section holds your own address watches — bank-qualified, up to 16 bytes each, editable, with hex/decimal/binary tooltips. | |
| 401 |
| [**Memory**](#memory) | A hex editor with three tabs: **CPU** (the CPU map), **Banked** (browse any RAM bank at `$A000`–`$BFFF`) and **VRAM** (VERA's full 17-bit address space). Drag to select a range, search by hex bytes or ASCII with Find Next/Prev, jump to an address, and watch changed bytes flash. Right-click to *Add to watch*, *Add range to watch*, *Copy address*, *Break on write* or *Clear selection*. Edits go through the normal write path, so I/O side effects happen and watchpoints fire. | |
| 402 |
| [**Source**](#source-level-stepping) | Your original `.s`/`.c` source in tabs, with the current line highlighted and centred on each stop. For C, stepping moves a whole statement at a time rather than an instruction. Right-click for *Run to here* and *Toggle breakpoint*. **Open…** pre-loads a file so you can set breakpoints before the PC ever gets there. Hovering a label or number resolves it to an address and its live value. | |
| 403 |
| [**VERA**](#vera-graphics-debugging) | Six tabs: **Registers** (all 32 registers `$9F20`–`$9F3F`, fields decoded), **Palette**, **Tiles**, **Sprites**, **Bitmap** and **Tilemap**. Each view decodes using the registers that actually rendered each scanline, so raster splits show up correctly rather than being flattened to the end-of-frame state. | |
| 404 |
| [**Breakpoints**](#breakpoints) | Every breakpoint, with its condition and hit count. Enable, disable or delete individually. | |
| 405 |
| [**Symbols**](#symbols) | A filterable list of every label from your `.dbg`, with live values. Right-click to *Go to*, *Toggle breakpoint* or *Run to here*. | |
| 406 |
| [**Call Stack**](#call-stack) | A heuristic 65xx stack unwind. Frames are named after the nearest enclosing label, so even code without debug info gets a useful name. Click a frame to jump there in both source and disassembly. | |
| 407 |
| [**PSG**](#psg--veras-16-voice-sound-generator) | VERA PSG voices with live register values, plus scope traces. | |
| 408 |
| [**YM2151**](#ym2151--the-fm-chip) | FM channel state and scope traces. | |
| 409 |
| [**PCM**](#pcm--the-audio-fifo) | VERA PCM state and scope traces. | |
| 410 |
| [**I/O**](#watching-io-and-file-access) | What the machine is doing to its ports. An **Activity** log of register accesses and decoded device events, each row naming and explaining the register it touched; **SD Card** command, block and status state; **Files** — every file the machine has opened, by name, on either file path; **Joysticks** with buttons decoded and lit live; **VIA**, **I2C** (with the SMC and RTC behind it) and **Serial**, each annotated with what the bits are wired to. | |
| 411 |
| [**Performance**](#performance-budgets) | How much of a frame your code actually uses. Work / interrupt / waiting split against a 60, 30 or arbitrary fps budget, a per-frame history graph with the budget drawn across it, rolling min/mean/p50/p95/p99/max with budget overruns, and a per-routine breakdown by address zone or guest marker. | |
| 412 |
|
| 413 |
While the machine is paused, the audio panels keep drawing their scope traces by projecting from |
| 414 |
the current register state, so you can see what a voice *would* be doing at the moment you stopped. |
| 415 |
|
| 416 |
#### Toolbar |
| 417 |
|
| 418 |
 |
| 419 |
|
| 420 |
Along the top, after the menus: **Continue**, **Pause**, **Step Into**, **Step Over**, |
| 421 |
**Step Out** (each greyed out when it does not apply and each showing its shortcut in the |
| 422 |
tooltip), then the speed control, then a status readout. |
| 423 |
|
| 424 |
The speed control shows the emulated clock as a real number — `1.75MHz`, `800kHz`, or `warp` — |
| 425 |
rather than a percentage, coloured blue below native speed, **red above it**, and orange in warp |
| 426 |
mode. `-` and `+` step the speed, `1x` returns to the machine's own clock, and `Warp` removes the |
| 427 |
limit entirely. |
| 428 |
|
| 429 |
The status readout shows `RUNNING`/`PAUSED`, an `IRQ` marker (with nesting depth) while the PC is |
| 430 |
inside an interrupt handler, the 24-bit PC as `KK:PPPP`, and the elapsed cycle and instruction counts. |
| 431 |
|
| 432 |
#### Menus |
| 433 |
|
| 434 |
* **View** — show or hide any panel. |
| 435 |
* **Layout** — *Reset to Default Layout* reopens everything and rebuilds the default docking arrangement. |
| 436 |
* **System** — *Reset* (`Ctrl+Shift+F5`, keeps your breakpoints), *Trigger IRQ*, *Trigger NMI*, and *Settings…*. |
| 437 |
* **Help** — the keyboard shortcut table, so you never need this README for it. |
| 438 |
|
| 439 |
**Settings…** covers interrupt following, break-on-interrupt, whether a break auto-switches you to |
| 440 |
the Disassembly or Source panel, memory change highlighting and its duration, holding audio while |
| 441 |
paused, I/O capture and its per-device gating, and the global interface scale. Settings and your |
| 442 |
window layout are saved to `imgui.ini` and restored next run. |
| 443 |
|
| 444 |
 |
| 445 |
|
| 446 |
#### Keyboard shortcuts |
| 447 |
|
| 448 |
Active when the debugger window has focus and you are not typing into a search or goto box. |
| 449 |
|
| 450 |
|
| 451 |
|
| 452 |
| `F5` | Continue (while paused) | |
| 453 |
| `Break` / `Pause` | Pause (while running) | |
| 454 |
| `F11` | Step into | |
| 455 |
| `Shift`+`F11` | Step out | |
| 456 |
| `F10` | Step over | |
| 457 |
| `Ctrl`+`F10` | Run to cursor | |
| 458 |
| `F9` | Toggle a breakpoint at the PC | |
| 459 |
| `Ctrl`+`Shift`+`F5` | Reset the machine, keeping breakpoints | |
| 460 |
| `Ctrl`+`Mouse wheel` | Zoom the font of the panel under the cursor | |
| 461 |
|
| 462 |
On macOS these are the physical `Control` and `Shift` keys, not `Command`. |
| 463 |
|
| 464 |
These work in the emulator window and are handy while debugging graphics: |
| 465 |
|
| 466 |
|
| 467 |
|
| 468 |
| `Ctrl`+`F3` | Toggle VERA layer 0 | |
| 469 |
| `Alt`+`F3` | Toggle VERA layer 1 | |
| 470 |
| `Shift`+`F3` | Toggle sprites | |
| 471 |
| `Shift`+`F8` | Toggle KERNAL skip | |
| 472 |
|
| 473 |
### A tour of the panels |
| 474 |
|
| 475 |
Every screenshot below comes from one session: a cc65 assembly program built with `ca65 -g`, run as |
| 476 |
|
| 477 |
``` |
| 478 |
x16emu -imgui -prg demo.prg -run -dbgfile demo.dbg -srcpath src |
| 479 |
``` |
| 480 |
|
| 481 |
#### Source-level stepping |
| 482 |
|
| 483 |
 |
| 484 |
|
| 485 |
The Source panel shows the file the PC is actually in — comments, labels, original formatting and |
| 486 |
all — with the current line highlighted and centred on every stop. Files open as tabs and the panel |
| 487 |
switches between them on its own as execution crosses module boundaries, so a program split across |
| 488 |
a dozen `.s` files needs no bookkeeping from you. |
| 489 |
|
| 490 |
`F10` walks the highlight down the loop an instruction at a time while `A` and `X` track along in |
| 491 |
the CPU panel. **Open…** pre-loads a file the PC has not reached yet, so you can arm a breakpoint |
| 492 |
before it ever gets there, and hovering a label or a literal resolves it to an address and its |
| 493 |
live value. |
| 494 |
|
| 495 |
#### Disassembly |
| 496 |
|
| 497 |
 |
| 498 |
|
| 499 |
When the PC lands somewhere the debug info does not cover — KERNAL ROM, a trampoline, someone |
| 500 |
else's binary — the Disassembly panel takes over. Every operand is resolved to its effective |
| 501 |
address and annotated with the symbol living there (`=08AD point_at_cell`), so ROM code reads |
| 502 |
almost as well as your own. Right-click any line for *Run to here* or *Toggle breakpoint*. |
| 503 |
|
| 504 |
Which panel comes to the front on a break is a preference, not a rule: **Settings…** can switch to |
| 505 |
Disassembly whenever there is no source and switch back when there is. |
| 506 |
|
| 507 |
#### Call stack |
| 508 |
|
| 509 |
 |
| 510 |
|
| 511 |
The 65C02 has no frame pointer, so this is a heuristic unwind of the hardware stack — but it is a |
| 512 |
useful one. Each frame is named after the nearest enclosing label and resolved back to a source |
| 513 |
file and line, and frames that fall outside your debug info are still labelled with where they are |
| 514 |
(`[KERNAL/ROM]`, `[stub/KERNAL RAM]`) rather than dropped. Click any frame to send both the Source |
| 515 |
and Disassembly panels to it. |
| 516 |
|
| 517 |
#### Symbols |
| 518 |
|
| 519 |
 |
| 520 |
|
| 521 |
Every label from the `.dbg`, filterable by name, each with its address and the byte and word |
| 522 |
currently living there. Double-click to go to a symbol; right-click for *Toggle breakpoint* or |
| 523 |
*Run to here*. |
| 524 |
|
| 525 |
#### CPU, virtual registers and the stack |
| 526 |
|
| 527 |
 |
| 528 |
|
| 529 |
Registers are shown at the width the CPU is actually using — the panel is 65C816-aware, so `A`, |
| 530 |
`X` and `Y` widen and narrow with the `M` and `X` flags instead of always reading as eight bits. |
| 531 |
Status flags are broken out as individual checkboxes, and every field is editable in place. |
| 532 |
|
| 533 |
Two things here are specific to the X16. **Virtual Regs (R0–R15)** decodes the sixteen 16-bit |
| 534 |
pseudo-registers the KERNAL and cc65 keep at `$02`–`$21`, so you read `R3` rather than working out |
| 535 |
which zero-page pair that was. The **Interrupts** block reports whether you are currently inside a |
| 536 |
handler and how deep, the VERA `IEN`/`ISR` state, and how many cycles remain until the next VSYNC. |
| 537 |
|
| 538 |
Below that sit the stack, most recent push first, and a **Watch** section for your own |
| 539 |
bank-qualified address watches — up to 16 bytes each, editable, with hex/decimal/binary tooltips. |
| 540 |
|
| 541 |
#### Memory |
| 542 |
|
| 543 |
 |
| 544 |
|
| 545 |
Three tabs: **CPU** for the processor's view, **Banked** for any RAM bank at `$A000`–`$BFFF`, and |
| 546 |
**VRAM** for VERA's full 17-bit space. Jump to an address, search by hex bytes or ASCII, drag to |
| 547 |
select a range, and watch bytes flash as they change. The selection is decoded underneath as |
| 548 |
decimal, hex and binary in the type of your choosing. |
| 549 |
|
| 550 |
Edits go through the emulator's normal write path rather than poking the array behind its back, so |
| 551 |
I/O side effects happen and watchpoints fire exactly as they would have if the program had done it. |
| 552 |
|
| 553 |
#### Breakpoints |
| 554 |
|
| 555 |
Set one by clicking the gutter in the Source or Disassembly panel, pressing `F9` at the PC, or |
| 556 |
right-clicking the line: |
| 557 |
|
| 558 |
 |
| 559 |
|
| 560 |
##### Conditions and hit counts |
| 561 |
|
| 562 |
A breakpoint that fires on all sixteen passes of a loop is rarely what you want. Every breakpoint |
| 563 |
carries an optional condition and ignore count, editable from the Breakpoints panel — no DAP client |
| 564 |
required: |
| 565 |
|
| 566 |
 |
| 567 |
|
| 568 |
The left operand is a register (`A`, `X`, `Y`, `SP`, `P`) or memory (`byte[addr]`, `word[addr]`); |
| 569 |
the comparison and the value complete it. The value box is **hex** — it is labelled with a `$` — |
| 570 |
so `$0A` is decimal ten: |
| 571 |
|
| 572 |
 |
| 573 |
|
| 574 |
Run, and the loop stops on exactly the pass you asked for — the eleventh, with the emulator having |
| 575 |
printed `0` through `10` and `X` still reading `$0A` because the `INX` has not happened yet: |
| 576 |
|
| 577 |
 |
| 578 |
|
| 579 |
The panel lists every breakpoint with its bank, hit count and condition, and has both a |
| 580 |
**Memory writes** section for watchpoints and a *Run to* box: |
| 581 |
|
| 582 |
 |
| 583 |
|
| 584 |
Conditions and hit counts set from a DAP client appear here too, and vice versa; the DAP syntax is |
| 585 |
documented under [Remote debugging with DAP](#remote-debugging-with-dap). |
| 586 |
|
| 587 |
##### Catching early boot with `-bp` |
| 588 |
|
| 589 |
Every other way of setting a breakpoint needs the machine to already be up and you to already be |
| 590 |
looking at it. `-bp` is the one that does not: it arms the breakpoint *before the first |
| 591 |
instruction runs*, which is the only way to stop inside something that has already finished by |
| 592 |
the time you could click a gutter — a `.prg` that runs from its load address, KERNAL or ROM code |
| 593 |
during start-up, or anything reached from the reset vector. |
| 594 |
|
| 595 |
The usual pairing is with the graphical debugger, so the machine is already stopped at the |
| 596 |
interesting place when the window appears: |
| 597 |
|
| 598 |
``` |
| 599 |
x16emu -imgui -prg myprog.prg -run -bp C000 |
| 600 |
``` |
| 601 |
|
| 602 |
It composes the same way with the other front ends, and can be repeated: |
| 603 |
|
| 604 |
``` |
| 605 |
x16emu -imgui -bp C000 -bp 05:A000 # several, one pinned to RAM bank 5 |
| 606 |
x16emu -debugport -bp C000 # stop at boot, attach an editor afterwards |
| 607 |
x16emu -bp C000 # no front end named, so -debug opens |
| 608 |
``` |
| 609 |
|
| 610 |
A breakpoint is no use without something able to resume from it, so `-bp` opens the text |
| 611 |
debugger when you have not asked for another front end. Name `-imgui` or `-debugport` and it |
| 612 |
leaves your choice alone. `-wp <address>[,<length>]` works the same way for writes. |
| 613 |
|
| 614 |
The breakpoint belongs to the command line, not to whichever debugger displays it. It shows up |
| 615 |
in the Breakpoints panel and to a DAP client, and neither can take it away — a client |
| 616 |
disconnecting leaves it exactly where you put it. Deleting it is a deliberate act: `F9` on the |
| 617 |
line, or the bin in the Breakpoints panel. |
| 618 |
|
| 619 |
#### Watchpoints — break on write |
| 620 |
|
| 621 |
A breakpoint stops when the PC reaches an address. A watchpoint stops when the *program writes to |
| 622 |
memory*, which is how you find what is corrupting a variable when you have no idea which code is to |
| 623 |
blame. Right-click any byte in the Memory panel — or drag-select a range first, for up to 64 active |
| 624 |
watchpoints: |
| 625 |
|
| 626 |
 |
| 627 |
|
| 628 |
Each watchpoint can carry a value filter, so you stop only when a particular value is written |
| 629 |
rather than on every store: |
| 630 |
|
| 631 |
 |
| 632 |
|
| 633 |
Run, and the eight earlier writes (`01 02 04 08 10 20 40 80`) pass straight through: |
| 634 |
|
| 635 |
 |
| 636 |
|
| 637 |
Execution stops on the instruction after the store that actually mattered: |
| 638 |
|
| 639 |
 |
| 640 |
|
| 641 |
> **A note on names.** The debugger deliberately avoids the word *watchpoint* in the UI, because |
| 642 |
> the CPU panel's **Watch** list already means something else — addresses you are keeping an eye |
| 643 |
> on, which never stop execution. In the UI it is *Break on write* in the Memory panel's |
| 644 |
> right-click menu and **Memory writes** in the Breakpoints panel. |
| 645 |
|
| 646 |
#### VERA graphics debugging |
| 647 |
|
| 648 |
The VERA panel has six tabs. All of them read the chip directly rather than the finished frame, so |
| 649 |
they are live and correct even while the machine is stopped and the emulator window is showing a |
| 650 |
stale image — and crucially, each view decodes using the registers that actually rendered each |
| 651 |
scanline, so **raster splits show up as raster splits** instead of being flattened to whatever the |
| 652 |
registers happened to hold at end of frame. |
| 653 |
|
| 654 |
##### Registers |
| 655 |
|
| 656 |
 |
| 657 |
|
| 658 |
All 32 registers at `$9F20`–`$9F3F` in hex and binary, plus a **Decoded** block that spells out |
| 659 |
what they add up to: the data port address and increment, output mode, which layers and sprites are |
| 660 |
on, scaling, border colour, and each layer's depth, map size, map base, tile base and tile size. |
| 661 |
|
| 662 |
##### Palette |
| 663 |
|
| 664 |
 |
| 665 |
|
| 666 |
All 256 entries from `$1FA00`–`$1FBFF` as swatches. Hover one for its index and 12-bit RGB value. |
| 667 |
|
| 668 |
##### Tiles |
| 669 |
|
| 670 |
 |
| 671 |
|
| 672 |
Decodes VRAM as tiles at whatever base address, depth, size, palette offset and zoom you point it |
| 673 |
at, so you can confirm your art landed correctly before anything is drawn with it. Hover a tile for |
| 674 |
its address; click to select. |
| 675 |
|
| 676 |
##### Tilemap |
| 677 |
|
| 678 |
 |
| 679 |
|
| 680 |
Renders a layer's map through that layer's own registers — the same tile base, map base, map size |
| 681 |
and depth the hardware is using. **Follow raster** decodes each row with the registers that were in |
| 682 |
force when that row was drawn, which is what makes mid-screen register changes visible. |
| 683 |
|
| 684 |
Here is the same map on the emulated display a moment later, once layer 0 was switched on and the |
| 685 |
text layer's opaque background was cleared: |
| 686 |
|
| 687 |
 |
| 688 |
|
| 689 |
##### Sprites |
| 690 |
|
| 691 |
 |
| 692 |
|
| 693 |
All 128 sprite records at `$1FC00`, fully decoded: a rendered preview of each sprite's image, its |
| 694 |
data address, colour depth, size, X/Y position, Z-depth, palette offset and flip bits. **Hide |
| 695 |
disabled** collapses the list to just the sprites that are actually live. |
| 696 |
|
| 697 |
##### Bitmap |
| 698 |
|
| 699 |
 |
| 700 |
|
| 701 |
Decodes a bitmap-mode layer straight out of VRAM at the width, height and zoom you choose. Because |
| 702 |
it reads VRAM rather than the display, the image is visible while it is still being written and |
| 703 |
before the layer has been switched on at all. |
| 704 |
|
| 705 |
#### Audio debugging |
| 706 |
|
| 707 |
All three sound sources get a panel, and each shows raw register state, a decode of what that state |
| 708 |
*means*, and scope traces. While the machine is paused the panels keep drawing by projecting from |
| 709 |
the current register state, so you can see what a voice *would* be doing at the moment you stopped |
| 710 |
— the **Hold audio panels while paused** setting controls this. |
| 711 |
|
| 712 |
##### PSG — VERA's 16-voice sound generator |
| 713 |
|
| 714 |
 |
| 715 |
|
| 716 |
Every one of the 16 voices, with the raw frequency word decoded to Hz *and* to the nearest note |
| 717 |
with its cents error, the waveform, pulse width as a duty percentage, the volume both raw and after |
| 718 |
the hardware's lookup table, pan, and a live level meter. Selecting a voice expands the four |
| 719 |
registers behind it byte by byte. There are also **Registers** and **Scope** tabs, the latter with |
| 720 |
a trace per voice plus the summed output. |
| 721 |
|
| 722 |
##### YM2151 — the FM chip |
| 723 |
|
| 724 |
 |
| 725 |
|
| 726 |
All eight channels with their key code decoded to a note and a frequency, algorithm, feedback, |
| 727 |
pan, PMS/AMS, per-operator key-on state, envelope phase and output level. |
| 728 |
|
| 729 |
The **Algorithm** tab is the one worth knowing about: it draws the operator routing for the |
| 730 |
selected channel from the registers that were actually written, and box brightness follows live |
| 731 |
envelope attenuation, so you can watch a patch sound rather than reading its bytes. |
| 732 |
|
| 733 |
 |
| 734 |
|
| 735 |
##### PCM — the audio FIFO |
| 736 |
|
| 737 |
 |
| 738 |
|
| 739 |
The audio FIFO is the one sound source where the CPU has to keep up, so this panel is mostly about |
| 740 |
whether it is: FIFO fill level as a bar and a byte count, how many frames that is in milliseconds, |
| 741 |
read and write indices, and whether the `AFLOW` interrupt is currently asserted. Format, volume and |
| 742 |
playback rate are decoded from `AUDIO_CTRL` and `AUDIO_RATE`, and the panel reads the PCM block |
| 743 |
directly rather than through `$9F3B`, so inspecting it does not itself consume audio. |
| 744 |
|
| 745 |
#### Watching I/O and file access |
| 746 |
|
| 747 |
The **I/O** panel covers the ports: everything in `$9F00`–`$9FFF`, plus the devices that hang off |
| 748 |
them. Its **Activity** tab is a rolling log rather than a state dump, because I/O is a |
| 749 |
conversation — a command goes out, a status comes back — and a snapshot taken between commands |
| 750 |
tells you nothing. Each row names the register it touched (`DATA0`, `T1C_L`, `SPI_CTRL`) and |
| 751 |
explains it on hover, so you are not looking up addresses in a manual while you debug. |
| 752 |
|
| 753 |
**Capture is off by default, and so is every device.** It is the one debugger feature the running |
| 754 |
machine pays for — a test and a branch on every I/O access — and the I/O page is the busiest |
| 755 |
address range in the system. Turn it on, then tick only what you are actually looking for. Each |
| 756 |
device's checkbox says what it captures, what it tells you that its own tab does not, and roughly |
| 757 |
how fast it produces events: VERA's data ports alone are thousands of accesses per frame, and I2C |
| 758 |
and the joysticks are polled around sixty times a second whether or not anything happens. The SD |
| 759 |
card's data path at `$9F3E`/`$9F3F` is captured separately as **SPI** so it survives VERA being |
| 760 |
off. |
| 761 |
|
| 762 |
Every other tab works with capture off — they read live device state, not the log. |
| 763 |
|
| 764 |
##### Two ways to reach a file, and only one of them has names |
| 765 |
|
| 766 |
This is the thing worth knowing before you go looking for a filename and cannot find it. |
| 767 |
|
| 768 |
|
| 769 |
|
| 770 |
| **Host filesystem** | the default, no SD image attached | Real filenames. The emulator intercepts the KERNAL's IEEE calls, so it knows the name, the channel, the direction and the byte counts. | |
| 771 |
| **SD card image** | `-sdcard <image>` | **Blocks only.** The ROM's own FAT driver runs inside the emulated machine and talks to the card over SPI. The emulator sees `CMD17`, an LBA and 512 bytes — never a name. | |
| 772 |
|
| 773 |
They are mutually exclusive by default: `-sdcard` turns host-filesystem access off unless you also |
| 774 |
pass `-hostfsdev`. `-fsroot` sets the host directory the machine sees — it does **not** attach a |
| 775 |
card, so with `-fsroot` alone the SD tabs are legitimately empty and say so. |
| 776 |
|
| 777 |
The **Files** tab shows both, in separate sub-tabs, so it is always clear which one is in play. |
| 778 |
|
| 779 |
Its host-filesystem half keeps an always-on **history** of opens, closes, directory listings, DOS |
| 780 |
commands and the status each produced. That history is the point: a file operation erases itself |
| 781 |
when it completes, so a list of *currently open* channels is empty almost all the time, and a |
| 782 |
directory listing or a failed open would otherwise flash past unseen. The history is kept whether |
| 783 |
or not capture is on, because file operations are rare enough to record for free. |
| 784 |
|
| 785 |
For the SD image, ADD closes the gap by parsing the filesystem *itself*: **Build index** reads the |
| 786 |
image's partition table, BPB, FATs and directory tree — including long filenames — and maps every |
| 787 |
cluster back to a path. Block traffic then resolves to `/GAMES/FOO.PRG + 2048`, and the file list |
| 788 |
shows how many bytes of each file the machine has actually read or written this session, listing |
| 789 |
the files it touched first so they stand out from the ones that merely exist. |
| 790 |
|
| 791 |
The index is a snapshot, not a live view. If the machine writes to a FAT or a directory the index |
| 792 |
is marked **STALE** and you rebuild it by hand. That is deliberate: quietly serving a filename |
| 793 |
that has since become wrong would be worse than admitting the index has aged. |
| 794 |
|
| 795 |
#### Performance budgets |
| 796 |
|
| 797 |
The 6502 never idles. It burns every cycle of a frame whether your code is working or spinning on |
| 798 |
a vsync flag, so "cycles per frame" is always 100% and tells you nothing. The **Performance** |
| 799 |
panel answers the question you actually have — *how much of the frame is my code using, and how |
| 800 |
much room is left?* — by telling **work** apart from **waiting**. |
| 801 |
|
| 802 |
Pick a target frame rate and it shows the split against that budget: a stacked bar of work, |
| 803 |
interrupt time and idle drawn to the width of the budget, so overrunning visibly runs off the end |
| 804 |
rather than quietly rescaling. Headroom is reported in cycles *and* scanlines, which is the unit |
| 805 |
most X16 code is written in. On an 8 MHz machine a VGA frame is exactly 134,400 cycles, or 256 per |
| 806 |
scanline. |
| 807 |
|
| 808 |
`Target FPS` asks how many vsyncs your frame work may span. The frame period is fixed by VERA, so |
| 809 |
asking for 30 fps does not make frames longer — it means the work may take two of them and the |
| 810 |
budget doubles. A target faster than the machine scans is the opposite question ("must this fit in |
| 811 |
half a frame?") and gets a fractional budget. |
| 812 |
|
| 813 |
Below that: a per-frame history graph with the budget drawn across it and overruns in red; rolling |
| 814 |
statistics over 1s / 10s / 30s / 60s / session with min, mean, p50, p95, p99, max and budget |
| 815 |
overruns; and a **breakdown** attributing cycles to named routines. Zones can be drawn by address |
| 816 |
— or picked straight off a symbol from your `.dbg`, since nobody knows the extent of |
| 817 |
`sprite_update` by heart — and a zone can be flagged *idle* to declare "cycles here are waiting". |
| 818 |
|
| 819 |
Waiting is found automatically: `WAI`, plus a store-free tight loop, which covers polling `$9F27` |
| 820 |
and polling a flag an IRQ handler sets. It cannot see a wait loop that stores or calls a |
| 821 |
subroutine — mark those as an idle zone, or bracket the real work with `$9FBC` markers from the |
| 822 |
guest for exact attribution. [docs/perf-budget.md](docs/perf-budget.md) documents the model and |
| 823 |
its limits in full. |
| 824 |
|
| 825 |
Everything here is also available over DAP — see |
| 826 |
[Profiling the guest](#profiling-the-guest-how-much-of-a-frame-is-your-code-using) — so CI can |
| 827 |
watch for regressions without a human reading a panel. |
| 828 |
|
| 829 |
The panel is open and docked with the other bottom-row views by default, so a normal `-imgui` |
| 830 |
session profiles from boot — the numbers are only useful if they are already there when you go |
| 831 |
looking. That costs the running machine an add and a table lookup per instruction; close the tab |
| 832 |
and it stops. |
| 833 |
|
| 834 |
### Source-level debugging with cc65 |
| 835 |
|
| 836 |
Build with debug info, and the emulator will show you your own source instead of raw disassembly. |
| 837 |
|
| 838 |
Build, in assembly: |
| 839 |
|
| 840 |
``` |
| 841 |
ca65 --debug-info -o myprog.o myprog.s |
| 842 |
ld65 -C myprog.cfg --dbgfile myprog.dbg -o myprog.prg myprog.o |
| 843 |
``` |
| 844 |
|
| 845 |
Build, in C: |
| 846 |
|
| 847 |
``` |
| 848 |
cl65 -t cx16 -g -Wl --dbgfile,myprog.dbg -o myprog.prg myprog.c |
| 849 |
``` |
| 850 |
|
| 851 |
Run: |
| 852 |
|
| 853 |
``` |
| 854 |
x16emu -imgui -prg myprog.prg -run -dbgfile myprog.dbg -srcpath ./src |
| 855 |
``` |
| 856 |
|
| 857 |
There are two ways a `.dbg` gets loaded: |
| 858 |
|
| 859 |
* `-dbgfile <file.dbg>` loads one explicitly at start-up. |
| 860 |
* Whenever the machine loads a program from the host filesystem — `-prg`, a BASIC `LOAD`, or a DOS |
| 861 |
shortcut like `/PROG.PRG` — the `.dbg` sitting beside it is merged automatically. The rule is |
| 862 |
simply to swap the extension: `myprog.prg` → `myprog.dbg`. Debug info describing the address |
| 863 |
range being replaced is dropped first, so an overlay loading over another module takes over its |
| 864 |
addresses, and swapping the first one back restores it. |
| 865 |
|
| 866 |
Runtime loading follows the debugger: it happens whenever the debugger is enabled. `-no-dbgauto` |
| 867 |
turns it off, and `-dbgauto` forces it on without the debugger. It is worth turning off for |
| 868 |
software you do not trust, because the file being read is chosen by the emulated program, and a |
| 869 |
`.dbg` can name source paths anywhere on the host. |
| 870 |
|
| 871 |
Two limits are worth knowing. Loads that do not use the KERNAL's block-transfer path are not |
| 872 |
noticed — `VERIFY`, loading into VRAM, and loading into `$9D00`–`$9FFF` all fall back to |
| 873 |
byte-at-a-time reads. And editing a `.dbg` while the emulator is running has no effect, because |
| 874 |
each file is merged once; restart the emulator after a rebuild. |
| 875 |
|
| 876 |
Because a `.dbg` only records source *file names*, the emulator locates the actual `.s`/`.c` files |
| 877 |
by searching, in order: the path stored in the `.dbg`, the directory of the `.dbg`, each |
| 878 |
`-srcpath <dir>` you passed (most recent first), the directory of the loaded program, and finally |
| 879 |
the current directory. |
| 880 |
|
| 881 |
**C programs show the C.** cc65 compiles `myprog.c` to `myprog.s` and assembles that, and |
| 882 |
with `-g` the `.dbg` describes the same bytes twice: once against your C statements and once |
| 883 |
against the generated assembly, instruction by instruction. The debugger prefers the C, so the |
| 884 |
Source panel follows your `.c` file and breakpoints set on a `.c` line land at the start of that |
| 885 |
statement. The generated `.s` is left out of the source panel's **Open…** list, since `cl65` |
| 886 |
deletes it on the way out; a `.s` you wrote yourself is still offered, and so is one you kept. |
| 887 |
The disassembly is unaffected — it still aligns on the real instruction boundaries. |
| 888 |
|
| 889 |
Stepping runs a whole C statement per press. F10 steps over a call — `printf(...)` and |
| 890 |
everything it does is one press — and F11 steps into one, landing on the first line of the |
| 891 |
function. Both fall back to stepping one instruction at a time when the debug info has no C in |
| 892 |
it, so assembly projects step exactly as they always did; the Source panel has a **Step by |
| 893 |
line** toggle if you want the instruction-at-a-time behaviour for C too. The Disassembly panel |
| 894 |
is unaffected either way. |
| 895 |
|
| 896 |
What you do *not* get for C yet is data: locals, parameters and watch expressions still have to |
| 897 |
be read as raw memory, because nothing here decodes cc65's software stack or its type records. |
| 898 |
Globals work, as they are ordinary labels. |
| 899 |
|
| 900 |
**Banked code is partly handled.** A `.dbg` does not record which RAM bank a `$A000`–`$BFFF` |
| 901 |
segment belongs to. When the running machine loads a program, the emulator notes which RAM bank was |
| 902 |
mapped as the first byte landed, and associates that bank with the segment — but only when the |
| 903 |
load's start address and size identify exactly one segment. A file containing several segments, or |
| 904 |
one whose size does not match a segment, leaves those segments bank-unknown. Nothing consumes that |
| 905 |
association yet; it is recorded for the debugger front-ends still to come. |
| 906 |
|
| 907 |
### Remote debugging with DAP |
| 908 |
|
| 909 |
`-debugport [<port>]` starts a [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) |
| 910 |
server over TCP, on port **9009** by default, so an IDE such as VS Code or Visual Studio can |
| 911 |
attach for source-level debugging. It speaks standard `Content-Length`-framed JSON, so any |
| 912 |
compliant DAP client works. |
| 913 |
|
| 914 |
``` |
| 915 |
x16emu -debugport -prg myprog.prg -run -dbgfile myprog.dbg -srcpath ./src |
| 916 |
x16emu -debugport 4711 ... # pick your own port |
| 917 |
x16emu -debugport -imgui ... # attach an IDE and watch the panels at the same time |
| 918 |
``` |
| 919 |
|
| 920 |
The emulator runs immediately on launch; it does not auto-pause waiting for a client. |
| 921 |
|
| 922 |
A minimal VS Code attach configuration looks like this — note that the `type` value depends on |
| 923 |
which DAP extension you wire it up to, as no editor extension is bundled with the emulator: |
| 924 |
|
| 925 |
```json |
| 926 |
{ |
| 927 |
"version": "0.2.0", |
| 928 |
"configurations": [ |
| 929 |
{ |
| 930 |
"name": "Attach to X16 Emulator", |
| 931 |
"request": "attach", |
| 932 |
"type": "x16", |
| 933 |
"host": "localhost", |
| 934 |
"port": 9009 |
| 935 |
} |
| 936 |
] |
| 937 |
} |
| 938 |
``` |
| 939 |
|
| 940 |
#### What the server supports |
| 941 |
|
| 942 |
Standard requests: `initialize`, `launch`, `attach`, `configurationDone`, `threads`, `stackTrace`, |
| 943 |
`scopes`, `variables`, `setVariable`, `setBreakpoints`, `setFunctionBreakpoints`, |
| 944 |
`setInstructionBreakpoints`, `setDataBreakpoints`, `setExceptionBreakpoints`, `breakpointLocations`, |
| 945 |
`continue`, `next`, `stepIn`, `stepOut`, `pause`, `goto`, `gotoTargets`, `stepInTargets`, |
| 946 |
`evaluate`, `readMemory`, `writeMemory`, `disassemble`, `loadedSources`, `source`, `restart`, |
| 947 |
`disconnect` and `terminate`. |
| 948 |
|
| 949 |
Custom requests: `x16/registers`, `x16/sendKey`, `x16/type`, `x16/joystick`, `x16/perfStats` and |
| 950 |
`x16/perfConfig`. The server also emits a custom `x16/perfBudgetOverrun` event. Clients can detect |
| 951 |
the profiling surface from the `supportsX16PerfStats` capability in the `initialize` response. |
| 952 |
|
| 953 |
Four scopes are exposed on every stack frame: **Registers** (including the 65C816 extras and the |
| 954 |
current RAM/ROM bank), **Virtual Registers** (R0–R15), **Zero Page** and **Stack**. Memory reads |
| 955 |
and writes use 24-bit addresses, so the high byte selects the 65C816 program bank on a GS machine. |
| 956 |
|
| 957 |
##### Reading and writing VRAM |
| 958 |
|
| 959 |
VERA's video RAM is not in the CPU map — the CPU reaches it only through the data port, one |
| 960 |
auto-incrementing byte at a time — so `readMemory` and `writeMemory` take a `vram:` prefix on |
| 961 |
`memoryReference` to address VERA's 17-bit space directly: |
| 962 |
|
| 963 |
``` |
| 964 |
readMemory { "memoryReference": "vram:1B000", "count": 32 } |
| 965 |
writeMemory { "memoryReference": "vram:1B000", "data": "3q2+7w==" } |
| 966 |
``` |
| 967 |
|
| 968 |
VRAM addresses default to hex, with `$` and `0x` accepted too; a bare reference is still a CPU |
| 969 |
address. The echoed `address` keeps the prefix, so paging through VRAM by feeding it back with an |
| 970 |
`offset` stays in the right space. This is the only way to write VRAM over DAP, and unlike the |
| 971 |
`vram` console expression it is not capped at 128 bytes and returns binary rather than text. |
| 972 |
|
| 973 |
A reference that cannot be parsed is now rejected. It used to fall back to address 0, so a typo |
| 974 |
answered `success: true` with the bytes from the wrong memory and nothing to say so. |
| 975 |
|
| 976 |
#### Instruction breakpoints and banks |
| 977 |
|
| 978 |
`setInstructionBreakpoints` takes an `instructionReference` in either form: |
| 979 |
|
| 980 |
``` |
| 981 |
setInstructionBreakpoints { "breakpoints": [ { "instructionReference": "33:A555" } ] } |
| 982 |
setInstructionBreakpoints { "breakpoints": [ { "instructionReference": "0x33A555" } ] } |
| 983 |
``` |
| 984 |
|
| 985 |
`bb:aaaa` names a RAM/ROM window bank explicitly and means exactly what `-bp bb:aaaa` means, hex on |
| 986 |
both sides. In the packed 24-bit form the high byte is read as the machine could have produced it: |
| 987 |
the 65C816 program bank on a GS, and the window bank otherwise, since `read6502` forces the program |
| 988 |
bank to zero elsewhere. A bare 16-bit address names no bank and breaks in whichever one is mapped. |
| 989 |
|
| 990 |
Naming a bank for an address the window registers do not reach — anything below `$A000`, or a |
| 991 |
non-zero GS program bank — is refused with `verified: false` and a `message`, as is giving the bank |
| 992 |
twice. Previously a banked reference was recorded against the *program* bank, which on a non-GS |
| 993 |
machine is always zero, so the breakpoint matched nothing while the client had been told |
| 994 |
`verified: true`. An `offset` displaces the address within its bank and no longer carries into it. |
| 995 |
|
| 996 |
#### Conditional breakpoints |
| 997 |
|
| 998 |
Put a condition string on a breakpoint in `setBreakpoints`. Terms are joined with `&&`. |
| 999 |
|
| 1000 |
* **Pin to a bank:** `bank == 2`, `bank 2`, `rombank == 3`. Decimal or `$XX`/`0xXX`. |
| 1001 |
* **Test a value:** operands `A`, `X`, `Y`, `SP`, `P`, `byte[$ADDR]`, `word[$ADDR]`, or a bare |
| 1002 |
`$ADDR` (which means `byte[$ADDR]`). Operators `==`, `!=`, `<`, `<=`, `>`, `>=`. |
| 1003 |
|
| 1004 |
``` |
| 1005 |
A == $05 |
| 1006 |
byte[$1234] != 0 |
| 1007 |
word[$0002] >= $0100 |
| 1008 |
bank == 0 && byte[$1234] != 0 |
| 1009 |
``` |
| 1010 |
|
| 1011 |
A `hitCondition` of `"5"` stops on the fifth hit. |
| 1012 |
|
| 1013 |
#### Expressions you can evaluate |
| 1014 |
|
| 1015 |
These work in a watch or REPL window, in addition to plain registers and addresses: |
| 1016 |
|
| 1017 |
|
| 1018 |
|
| 1019 |
| `$C000`, `$01C000`, `0xC000` | The byte at that CPU address (24-bit; high byte is the 65C816 bank) | |
| 1020 |
| `A`, `X`, `Y`, `SP`, `PC`, `P` | Register values, sized for the current CPU mode | |
| 1021 |
| `regs_all` | A one-line summary of every register | |
| 1022 |
| `vram ADDR [COUNT]` | VERA VRAM bytes, from a 17-bit hex address (capped at 128; for bulk or binary access use `readMemory` with a `vram:` reference) | |
| 1023 |
| `vera_reg` | All 32 VERA registers | |
| 1024 |
| `vera_line LINE` | The layer registers that actually rendered that display scanline — the way to catch a raster split | |
| 1025 |
| `bp_add ADDR`, `bp_remove ADDR`, `bp_list`, `bp_clear` | Manage breakpoints | |
| 1026 |
| `watch_add ADDR [LEN]`, `watch_remove ADDR`, `watch_list`, `watch_clear` | Manage write watchpoints | |
| 1027 |
| `reset` | Reset the machine | |
| 1028 |
|
| 1029 |
cc65 labels and equates from a loaded `.dbg` resolve too. |
| 1030 |
|
| 1031 |
#### Scripting input for automated tests |
| 1032 |
|
| 1033 |
Three custom requests let a script drive the machine, which is what makes automated regression |
| 1034 |
runs of a game practical: |
| 1035 |
|
| 1036 |
|
| 1037 |
|
| 1038 |
| `x16/sendKey` | `key` (SDL key name like `"Return"`/`"A"`/`"Left"`, or a numeric scancode), `action` (`"press"` (default), `"down"`, `"up"`), optional `modifiers` (array of key names held around the key) | Injects a key event through the same path as a physical key press. | |
| 1039 |
| `x16/type` | `text` (string) | Types a string into the KERNAL keyboard buffer, like a clipboard paste. Ideal for BASIC lines and filenames; supports the `\Xnn` hex escape. | |
| 1040 |
| `x16/joystick` | `index` (port 0–3), `buttons` (array of `up`, `down`, `left`, `right`, `a`, `b`, `x`, `y`, `start`, `select`, `l`, `r`), optional `mask` (raw 16-bit active-low), `enabled` (`false` releases the port back to a physical controller; `true` on its own connects one with nothing held) | Holds or releases a virtual SNES controller. Buttons not listed are released, so one request sets the whole held state. | |
| 1041 |
|
| 1042 |
`x16/joystick` needs no gamepad and no `-joy1`..`-joy4` flag: driving a port enables it, so a |
| 1043 |
headless CI machine can play a game that reads the controller. Sending neither `buttons` nor |
| 1044 |
`mask` reports the port's state without changing it, which is how a test asserts what it just |
| 1045 |
pressed. Every request answers with the resulting `mask`, the held `buttons`, and whether a |
| 1046 |
physical controller is bound. |
| 1047 |
|
| 1048 |
While a port is held its shift register behaves as an attached controller's does, so the KERNAL |
| 1049 |
reports a joystick present — `JOY(1)` reads `$00` with nothing held rather than `$FF`. Releasing |
| 1050 |
it with `enabled: false` makes the port read empty again. |
| 1051 |
|
| 1052 |
There is also `x16/registers`, which returns the full CPU, KERNAL and VERA state as JSON in one call. |
| 1053 |
|
| 1054 |
#### Profiling the guest: how much of a frame is your code using? |
| 1055 |
|
| 1056 |
The 6502 never idles — it burns every cycle of a frame whether your code is working or spinning |
| 1057 |
on a vsync flag — so "cycles per frame" is always 100% and tells you nothing. The emulator |
| 1058 |
therefore classifies cycles as **work** or **waiting**, and reports the work against a frame |
| 1059 |
budget. It measures VERA's VRAM bandwidth alongside, because a program can have cycles to spare |
| 1060 |
and still run out of bus. See [docs/perf-budget.md](docs/perf-budget.md) for how the |
| 1061 |
classification works, what the bandwidth model is derived from, and where each can be fooled. |
| 1062 |
|
| 1063 |
|
| 1064 |
|
| 1065 |
| `x16/perfStats` | optional `windows` (array of seconds, or names like `"30s"`/`"session"`), `includeZones` (default true), `includeFrames` (raw per-frame samples, oldest first) | Returns the budget, the last completed frame, rolling statistics — min/mean/p50/p95/p99/max, budget overruns and the worst one — per window, and a `bandwidth` object with VERA's VRAM traffic. Asking arms both if they were off. | |
| 1066 |
| `x16/perfConfig` | `enabled`, `targetFps`, `idleMode` (`"auto"`/`"markers"`/`"none"`), `historyFrames`, `overrunEvents`, `zones` (array of `{name, start, end, bank, idle}`), `reset` | Configures the above. `zones` replaces the address zones wholesale. | |
| 1067 |
|
| 1068 |
The **`bandwidth`** object answers the other half of "what is eating my frame?": what VERA's VRAM |
| 1069 |
bus did, rather than what the CPU did. It reports per-layer fetches and bytes, the CPU's own |
| 1070 |
traffic through `$9F23`/`$9F24` — the one piece of VERA bandwidth a program controls directly — |
| 1071 |
and VERA FX amplification, where one `sta` can move four bytes. |
| 1072 |
|
| 1073 |
Its headline is `peakLineClocks` against `lineClocks` (800), **not** a percentage of VERA's total |
| 1074 |
bandwidth: a frame uses maybe a fifth of the chip's 100 MB/s, so percent-of-peak would be true and |
| 1075 |
useless. One scanline is the window that genuinely runs out. Sprites are last on that bus, so |
| 1076 |
`spriteBusHeadroomClocksAtPeak` is what the sprite renderer was really competing for. |
| 1077 |
|
| 1078 |
Every clock figure in `bandwidth` is **bus occupancy**, and `clockUnits` says so. That is a |
| 1079 |
different clock from the one the sprite views report: sprite render time is wall clock and runs |
| 1080 |
about five times higher, because it ticks while the renderer paints with the bus idle. Convert |
| 1081 |
before comparing the two — sprite render time has its own view in the VERA panel under |
| 1082 |
**Multiplex → Render time**. |
| 1083 |
|
| 1084 |
The emulator also pushes an **`x16/perfBudgetOverrun` event** when the guest misses its budget, |
| 1085 |
coalesced to at most one per second with a count and the worst frame in that period — so tooling |
| 1086 |
can spot a regression without polling. |
| 1087 |
|
| 1088 |
`targetFps` asks *how many vsyncs your frame work may span*. The frame period is fixed by VERA, so |
| 1089 |
asking for 30 fps does not make frames longer: it means the work may take two of them, and the |
| 1090 |
budget doubles. A target faster than the machine scans is the opposite question — "must this |
| 1091 |
routine fit in half a frame?" — and gets a fractional budget. On an 8 MHz machine a VGA frame is |
| 1092 |
exactly 134,400 cycles, or 256 per scanline. |
| 1093 |
|
| 1094 |
Profiling costs the running machine an add and a table lookup per instruction. The Performance |
| 1095 |
panel is open by default, so `-imgui` profiles from boot and closing the tab stops it; without the |
| 1096 |
panel, nothing is collected until a DAP client asks. |
| 1097 |
|
| 1098 |
#### The control port: the same numbers to a test harness |
| 1099 |
|
| 1100 |
`-controlport <port>` serves the [Retro Remote Debug Controller](https://oxygenhub.io/doomsdayone/retro-remote-debug-controller) |
| 1101 |
HTTP contract on `127.0.0.1:<port>`: the surface a pytest harness drives to read memory, step |
| 1102 |
frames deterministically, take screenshots and inject keys, pads and the pointer. It is the plain |
| 1103 |
fork's control port with one more level, **contract 0.6**, which adds the profiler: |
| 1104 |
|
| 1105 |
|
| 1106 |
|
| 1107 |
| `GET /perf?windows=1,30,session&frames=n&since=f` | The budget, the last completed frame (total, work, idle, IRQ, main), the statistics windows, the zones with their own windows, raw per-frame records (`since=` the last frame you saw, so none is missed), and `platform`: VERA's `bandwidth` (with `lines=1`, each scanline's clocks) and the sprite renderer's time per line (`sprites`). | |
| 1108 |
| `POST /perf` | A JSON body: `enabled`, `targetFps`, `idleMode`, `historyFrames`, `reset`, `zones` (address ranges, or `{"name", "marker"}` to name a guest's `$9FBC` region), and `sprites` (sprite render timing, off until asked). | |
| 1109 |
|
| 1110 |
The fields are `x16/perfStats`'s, from the same builders, so a number means the same thing |
| 1111 |
whichever way a client asked. `/perf` needs cJSON, as DAP does; without it the port serves 0.5. |
| 1112 |
|
| 1113 |
``` |
| 1114 |
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy ./x16emu -prg GAME.PRG -run -controlport 8391 & |
| 1115 |
curl -s -XPOST localhost:8391/perf -d '{"enabled":true,"zones":[{"name":"logic","marker":1}]}' |
| 1116 |
curl -s 'localhost:8391/perf?windows=1,session&frames=60' |
| 1117 |
``` |
| 1118 |
|
| 1119 |
Run it with a display driver, dummy or real, not `-headless`: the port's `/step` counts video |
| 1120 |
frames, which a headless run never completes. |
| 1121 |
|
| 1122 |
#### x16dbg, the bundled command-line client |
| 1123 |
|
| 1124 |
`tools/x16dbg` is a small .NET 8 console DAP client, useful for poking at a running emulator |
| 1125 |
without an IDE and for scripting. |
| 1126 |
|
| 1127 |
``` |
| 1128 |
dotnet run --project tools/x16dbg # interactive |
| 1129 |
dotnet run --project tools/x16dbg -- mem 1030-1040 # one-shot |
| 1130 |
dotnet run --project tools/x16dbg -- -host 127.0.0.1 -port 9009 |
| 1131 |
``` |
| 1132 |
|
| 1133 |
Options are `-host <ip>` (default `127.0.0.1`), `-port <port>` (default `9009`), `-i` to force |
| 1134 |
interactive mode, and `-h` for help. With no command it drops into an interactive prompt. |
| 1135 |
|
| 1136 |
Commands, in both modes: `step`/`s`, `continue`/`c`, `break`/`b`, `status`/`t`, `reset`, |
| 1137 |
`regs`/`r`, `setreg <reg> <hexval>`, `mem <addr>-<end>` or `mem <addr> <len>`, |
| 1138 |
`setmem <addr> <hexdata>`, `vram`/`v` and `setvram` for VERA's video RAM, `joy`/`j` to drive a |
| 1139 |
controller port, and `bp add|remove|list|clear <addr>`. Addresses are hex. |
| 1140 |
|
| 1141 |
``` |
| 1142 |
> b |
| 1143 |
! STOPPED (pause) |
| 1144 |
PC=$C1C6 A=$02 ( 2) X=$FF (255) Y=$0A ( 10) |
| 1145 |
SP=$F0 P=$24 [--B-DI--] RAM Bank=$00 ROM Bank=$00 |
| 1146 |
> bp add 080D |
| 1147 |
> mem C000-C00F |
| 1148 |
C000: A9 00 85 00 A9 01 85 01 A9 00 8D 00 02 A9 00 8D |................| |
| 1149 |
> c |
| 1150 |
``` |
| 1151 |
|
| 1152 |
`vram` reads VERA's 17-bit video RAM, which is not in the CPU map — so `mem 1B000` and |
| 1153 |
`vram 1B000` are two different memories at the same number — and `setvram` is the only way to |
| 1154 |
write it. Both echo the bytes back after a write: |
| 1155 |
|
| 1156 |
``` |
| 1157 |
> vram 1B000 10 |
| 1158 |
1B000: DF 64 20 64 20 64 20 64 20 64 20 64 E9 64 20 61 |.d d d d d d.d a| |
| 1159 |
> setvram 1F9C0 CAFEBABE |
| 1160 |
Wrote 4 bytes to vram:1F9C0 |
| 1161 |
1F9C0: CA FE BA BE |.... | |
| 1162 |
``` |
| 1163 |
|
| 1164 |
`joy <port 1-4>` drives a virtual SNES controller with no gamepad attached and no `-joy1`..`-joy4` |
| 1165 |
flag. The button list is the complete held state, so anything left off it is released: |
| 1166 |
|
| 1167 |
``` |
| 1168 |
> joy 1 right a |
| 1169 |
Joystick 1: right a |
| 1170 |
mask=$FF7E source=virtual |
| 1171 |
> joy 1 start (right and a let go) |
| 1172 |
> joy 1 (query, changes nothing) |
| 1173 |
> joy 1 none (connected, nothing held — JOY(1) reads $00) |
| 1174 |
> joy 1 release (port empty again — JOY(1) reads $FF) |
| 1175 |
``` |
| 1176 |
|
| 1177 |
Buttons are `up down left right a b x y start select l r`. |
| 1178 |
|
| 1179 |
One caveat about one-shot mode: each command is its own debug session, and the emulator resumes |
| 1180 |
when a session that paused it goes away — deliberately, so a headless machine cannot be left |
| 1181 |
halted with nobody able to restart it. Commands that only inspect are fine, and memory, VRAM and |
| 1182 |
joystick state persist either way, but anything that depends on staying stopped (`break`, |
| 1183 |
`setreg`, stepping) belongs in interactive mode, where one session stays open. |
| 1184 |
|
| 1185 |
Web Site |
| 1186 |
-------- |
| 1187 |
|
| 1188 |
Commander X16: [https://commanderx16.com](https://commanderx16.com) |
| 1189 |
|
| 1190 |
X16Emu ADD: [https://github.com/xylothan/x16-emulator](https://github.com/xylothan/x16-emulator) |
| 1191 |
|
| 1192 |
|
| 1193 |
License |
| 1194 |
------- |
| 1195 |
|
| 1196 |
Copyright (c) 2019-2023 Michael Steil <[email protected]>, [www.pagetable.com](https://www.pagetable.com/), et al. |
| 1197 |
All rights reserved. License: 2-clause BSD |
| 1198 |
|
| 1199 |
ADD's additions are released under the same 2-clause BSD license. Dear ImGui is vendored in |
| 1200 |
`src/extern/imgui/` under its own MIT license. |
| 1201 |
|
| 1202 |
|
| 1203 |
Release Notes |
| 1204 |
------------- |
| 1205 |
See [RELEASES.md](RELEASES.md) for what each ADD build adds. The release notes for the upstream |
| 1206 |
emulator each build is based on are [published upstream][upstream-releases]. |
| 1207 |
|
| 1208 |
|
| 1209 |
|
| 1210 |
[dev-release]: https://github.com/xylothan/x16-emulator/releases/tag/dev |
| 1211 |
[releases]: https://github.com/xylothan/x16-emulator/releases |
| 1212 |
[upstream]: https://github.com/X16Community/x16-emulator |
| 1213 |
[upstream-readme]: https://github.com/X16Community/x16-emulator#readme |
| 1214 |
[upstream-releases]: https://github.com/X16Community/x16-emulator/releases |
| 1215 |
[webassembly]: https://github.com/xylothan/x16-emulator/blob/main/webassembly/WebAssembly.md |
| 1216 |
[x16docs]: https://github.com/X16Community/x16-docs |
| 1217 |
[x16rom-build]: https://github.com/X16Community/x16-rom#releases-and-building |
| 1218 |
[x16rom]: https://github.com/X16Community/x16-rom |
| 1219 |
[website]: https://commanderx16.com |
| 1220 |
|
| 1221 |
|
| 1222 |
<div class="page-break"></div> |