Ultimate Tool C64 Protocols #
Ultimate Tool currently uses three independent fixed-memory protocols:
UTD2: C64-to-host diagnostic events plus cooperative liveness in 256 bytesUTX2: C64-to-host cooperative crash context and optional exact site in 32 bytesUTC1: one host-to-C64 startup command in 64 bytes
The project manifest declares their addresses. The Rust-MOS fixture reserves
$C000-$C0FF, $C100-$C13F, and $C140-$C15F as NOLOAD linker regions. All
multibyte integers are little-endian.
Diagnostic mailbox (UTD2 version 2) #
The host continues to parse legacy UTD1 version 1 mailboxes. UTD1 has no
heartbeat or crash terminal/event, so it cannot support opt-in hang detection.
Header #
| Offset | Size | Meaning |
|---|---|---|
| 0 | 4 | ASCII magic UTD2 |
| 4 | 1 | Protocol version, 2 |
| 5 | 1 | Header size, 16 |
| 6 | 1 | Record size, 60 |
| 7 | 1 | Record count, 4 |
| 8 | 2 | Latest published sequence; zero means no event |
| 10 | 1 | Terminal state: 0 running, 1 pass, 2 fail, 3 crash |
| 11 | 2 | Wrapping cooperative heartbeat counter |
| 13 | 3 | Reserved and zero |
Event records #
Four records begin at offset 16.
| Record offset | Size | Meaning |
|---|---|---|
| 0 | 2 | Event sequence |
| 2 | 1 | Event kind |
| 3 | 1 | Flags |
| 4 | 2 | Project-defined numeric code |
| 6 | 1 | Payload length, at most 52 |
| 7 | 1 | Reserved and zero |
| 8 | 52 | Printable ASCII payload |
Event kinds are ready, log, assertion pass, assertion fail, test pass, test
fail, and crash (1 through 7 respectively). A crash terminal requires
exactly one crash event and no pass/fail terminal event; every other terminal
rejects crash events.
Sequences use 1…65535, skip reserved sequence zero, and wrap to 1. The C64
advances the ring slot independently from the numeric sequence so all four
records remain available across that wrap.
The publisher clears a selected record sequence, writes its metadata and payload, publishes the record sequence, writes a terminal state when applicable, and publishes the header sequence last. No event may follow a terminal event.
DeveloperSession::heartbeat() advances the 16-bit heartbeat independently of
event publication and wraps through zero. The host begins monitoring only
after ready and startup-command acknowledgement. A heartbeat may advance
between the two stability reads without invalidating an otherwise stable event
publication; the host uses the heartbeat from the second read.
DeveloperSession::crash(code, label) publishes an explicit terminal crash
without an exact origin. The panic_halt helper reclaims an initialized UTD2
mailbox on the terminal panic path, publishes code 0x0001 and the stable
label panic, then halts. crash_here!(&mut session, code, label) is the
opt-in exact-site path described below.
The host reads the complete 256-byte mailbox twice and compares all publication state. UTD2 heartbeat bytes are excluded from that equality check because they are independently published liveness state. A stable snapshot must expose a contiguous ring ending at the published sequence. The host accumulates and deduplicates events, but any missing sequence is a hard protocol failure. After the first valid mailbox, three consecutive malformed stable reads fail the run and preserve the raw read pair.
Crash context (UTX2 version 2) #
Manifest schema 4 may opt into a fixed crash context plus bounded memory captures. The SDK clears the 32-byte context during initialization. A cooperative crash records the context before publishing its terminal event. The host continues to accept UTX1; those contexts have no exact site.
| Offset | Size | Meaning |
|---|---|---|
| 0 | 4 | ASCII magic UTX2 |
| 4 | 1 | Protocol version, 2 |
| 5 | 1 | Context size, 32 |
| 6 | 1 | Publication state: 1 writing, 2 complete |
| 7 | 1 | Register semantics: 1 capture-helper entry |
| 8 | 1 | Accumulator |
| 9 | 1 | X register |
| 10 | 1 | Y register |
| 11 | 1 | Processor status as pushed by PHP |
| 12 | 1 | Hardware stack pointer before the capture helper call |
| 13 | 2 | Crash code, which must match the crash event |
| 15 | 1 | Site semantics: 0 unavailable, 1 instrumented callsite |
| 16 | 2 | Runtime address after the instrumentation helper call, or zero |
| 18 | 14 | Reserved and zero |
The register values are the values available on entry to the SDK's assembly
capture helper. They are deliberately labeled capture_entry; they are not
represented as an exact snapshot of the instruction that caused a Rust panic.
As on a 6510, PHP forces the break and unused bits in the saved status
representation.
crash_here! invokes a dedicated 6510 assembly helper directly from the
instrumented function. The helper reads only its own JSR return slot, adds one
to normalize the 6510 return address to the following instruction, and records
that address before normal crash publication. It never scans arbitrary stack
words. DeveloperSession::crash and panic_halt publish UTX2 with site
semantics zero and address zero.
UTX1 uses the same bytes through offset 14 and requires bytes 15–31 to be zero.
After a valid crash terminal/event, the host reads the context twice and requires identical, complete publications. It then reads only:
- The configured number of hardware-stack bytes beginning at
$0100 + SP + 1, truncated at the end of the stack page - Explicit manifest-declared zero-page ranges
- Explicit manifest-declared global ranges
Configuration limits the stack to 64 bytes, each named range to 64 bytes,
eight ranges of each kind, and 256 captured bytes in total. Zero-page ranges
must remain below $0100; selected globals cannot overlap C64 I/O or protocol
regions. This slice does not perform arbitrary memory reads, source-address
resolution, stack walking, or register reconstruction.
Raw context, stack, zero-page, and global bytes are preserved alongside
schema-2 crash-state.json. Schema-1 crash-state evidence remains readable.
Each memory record includes its address, length, evidence filename, and
SHA-256.
Startup command (UTC1 version 1) #
The one-shot command slot has a 12-byte header followed by 52 combined bytes of command name and payload.
| Offset | Size | Meaning |
|---|---|---|
| 0 | 4 | ASCII magic UTC1 |
| 4 | 1 | Protocol version, 1 |
| 5 | 1 | Slot size, 64 |
| 6 | 1 | State: 1 waiting, 2 submitted, 3 accepted, 4 rejected |
| 7 | 1 | Reserved and zero |
| 8 | 2 | Request sequence |
| 10 | 1 | Command-name length; nonzero when submitted |
| 11 | 1 | Payload length |
| 12 | 52 | Printable ASCII command name followed by payload |
The device initializes the slot and publishes waiting after the diagnostic
mailbox is ready. The host requires both the ready event and two identical
waiting-slot reads before writing.
For VICE, the binary monitor stops execution while Memory Set writes the full slot. For an Ultimate, the runner pauses the CPU at the advertised application safe point, posts the 64 bytes through DMA, and resumes even when the write fails. Neither adapter retries an ambiguously accepted write.
The SDK validates the complete submitted slot, changes its state to accepted,
and returns an owned command to the application. Unknown application-level
commands are reported through normal diagnostic failure events. The current
fixture accepts command name scenario with payload pass, fail, crash,
panic, hang, timeout, or malformed.
Timeout classification #
Manifest schema 3 adds optional per-test heartbeat_timeout_ms. It must be
positive, shorter than the overall timeout, and at least three times the
largest configured target polling interval.
- A valid crash terminal/event is classified
crash. - A configured heartbeat that stops advancing is classified
hang. - An advancing heartbeat whose overall deadline expires is classified
timeout. - A missing
readyevent remains a readiness-stage timeout.
Without heartbeat_timeout_ms, the runner retains generic deadline behavior
and never relabels an uninstrumented program as hung.
DMA boundary #
The fixture performs no UCI transfers. The ready/waiting state is therefore a valid quiescent checkpoint for the single coordinated write. Future transports must add explicit application-level safe-point negotiation before mixing UCI activity with host DMA.