A DOS-style TUI that mirrors a Commodore 64 Ultimate screen live and remote-controls it — in your terminal or in a browser, from the same code.
It's a fourth face over the u64ctl device core (alongside u64ctl's CLI, MCP server, and WASM library), built on the foxpro-go FoxPro-for-DOS TUI framework.
┌ Text Screen ┐ ┌ Graphics ┐ ┌ Command Window ┐
│ 40x25 mirror│ │ 320x200 │ │ RESET TYPE RUN │ foxpro-go TUI
│ (cell text) │ │ pixels │ │ POKE PEEK … │ (terminal or canvas)
└─────────────┘ └──────────┘ └────────────────┘
└────────────── ops (u64ctl) ──────────────┘
│
u64 REST client ── HTTP /v1/... ── Ultimate 64
The terminal face: overlapping DOS-style windows — the 40×25 Text Screen, the
Command window (after HELP), a Sprite Inspector, and the live Screen mirror.
Two faces, one binary set.
go run . # start disconnected; File ▸ Connect…
go run . -host 192.168.1.64 # auto-connect (or set U64_HOST / U64_PASSWORD)
# or: make build -> bin/u64shellDiamond Mine running, with System ▸ Ultimate 64 Info open and a sprite in the inspector. (The Text Screen shows scrambled cells because the game runs in bitmap mode — the Screen window renders it correctly.)
make run-serve builds the WASM module and a small server that hosts it and
proxies device calls. Open the printed URL.
export U64_HOST=192.168.1.64
make run-serve # serves http://localhost:8064The same TUI runs on an HTML canvas (via foxpro-go's WASM bridge); device calls
go to /u64/... on the page's own origin, which the server forwards to the
Ultimate — so there's no browser CORS problem.
The same app in a browser: the Screen drawn with real pixels on an HTML canvas (Diamond Mine), with device calls routed through the server/broker ("Connected: via server").
- File ▸ Connect… / Disconnect — a modal dialog to point at a device; the
status bar (right) shows
Connected: <host>/Disconnected. - View — toggle windows. In the browser it's Screen + Sprite Inspector (Screen opens by default). In the terminal it's Text Screen (default) + Screen + Sprite Inspector.
- F2 (or File ▸ Command Window) — the command window. Type a line, Enter to
run;
HELPlists commands,HELP TYPEshows detail.
Windows:
| Window | What it shows |
|---|---|
| Screen | the live C64 display: the 320×200 window (text via the embedded char ROM, ECM-aware, hi-res/multicolor char + bitmap modes) plus a slice of the border (its $D020 color, and sprites parked in the border), with sprites composited on top. Real pixels on the browser canvas; Unicode half-blocks as the terminal fallback. Resizable, letterboxed to the C64 aspect. This is the C64 screen — in the browser it's the only display you need. |
| Text Screen (terminal only) | the 40×25 text screen decoded to characters in C64 colors (ECM-aware), as a readable cell grid. Fixed size. Redundant in the browser, where Screen renders text too. |
| Sprite Inspector | one hardware sprite zoomed (← → to change sprite). |
Command-window verbs (over u64ctl/ops):
| Command | Action |
|---|---|
RESET REBOOT MENU PAUSE RESUME |
machine control |
STOP |
send RUN/STOP (break a BASIC program) |
TYPE <text> |
type into the keyboard buffer — {return}/{enter}/{cr} = RETURN, {space} = a literal space |
RUN |
type RUN + RETURN |
PEEK <addr> / POKE <addr> <val> |
read / write one byte (hex needs $/0x) |
CLEAR HELP QUIT |
command-window built-ins |
The Screen is reconstructed from device memory — screen/color RAM, the VIC
registers, sprite data, and the character font — read about once a second. That
faithfully mirrors normal RAM-based programs (BASIC, most .prgs, demos),
including hi-res / Extended Background Color / multicolor character modes and
hi-res / multicolor bitmap modes.
Two things it can't fully reproduce:
- Cartridge graphics. A cartridge's font/graphics live in cartridge ROM,
which the device's memory read doesn't expose (it reads C64 RAM + I/O only).
For an Ultimax cart (many MAX-machine games, e.g. Kickman) the CPU has RAM
only at
$0000–$0FFF, so the font region reads back as$FFand every character renders as a solid colored block — you get the screen layout and colors, but not the glyph art. - Mid-frame raster tricks. A ~1 Hz memory snapshot can't capture per-scanline
mode/color changes, sprite multiplexing, or animated border effects (raster
bars, FLD, opened borders); it shows one consistent instant with a single
$D020border color. (Sprites parked in the border do show — that's static.)
The faithful fix for both is the Ultimate's VIC video stream (a future direction) — capturing the real VIC output instead of rebuilding it from memory.
The Ultimate's small HTTP server cannot handle concurrent requests — two
overlapping calls wedge it. The browser server (cmd/serve) is therefore the
single serialized broker: it forwards /u64/... to the device one request
at a time and answers /healthz with the device it bridges. The u64ctl CLI and
MCP server auto-detect a broker on the well-known port and route through it, so
the web UI, CLI, and Claude can all drive one C64 without colliding. Run them
direct with -direct.
The device's REST server is minimal; cmd/serve and u64ctl/u64 work around it:
- No concurrency — serialized by a mutex (
u64.Client) and the broker. - No request compression — Go's default
Accept-Encoding: gzipmakes the device gzip on its slow CPU (multi-second delays); compression is disabled. POST …writemem(body) is unreliable — "could not read data from attachment". Writes use the body-lessPUT …writemem?address=&data=<hex>form (chunked at 128 bytes), which the device's own web UI uses.- Sensitive to back-to-back connections — it replies
Connection: closeand drops packets under zero-gap bursts (→ ~2s TCP retransmits). The broker spaces requests with-min-gap(default 60ms).
main.go terminal entry (//go:build !js)
wasm_main.go browser entry (//go:build js && wasm) — SimulationScreen + foxpro-go/wasm
ui.go buildUI(): windows, menu, commands, poller — shared by both entries
screen.go Text Screen provider (cell text, ECM-aware)
graphics.go Graphics provider (320x200 pixels / half-blocks) + Sprite Inspector
c64render.go C64 palette + screen-code → Unicode glyph map
commands.go command-window verbs
conn.go connection state (host/password/client; pluggable dial for the proxy)
connectdialog.go modal Connect dialog
cmd/serve/ the broker: serves the embedded web app + forwards /u64 → device
make build # terminal binary -> bin/u64shell
make wasm # browser module -> cmd/serve/web/u64shell.wasm (+ wasm_exec.js)
make serve # build wasm, then the server -> bin/u64shell-serve
make run-serve # build + run the server (HOST=<ip> or U64_HOST)Local cross-module development resolves u64ctl / foxpro-go / go6asm through the
parent go.work (and replace directives in go.mod).


