wish.ultimate¶
A Commodore 64 Ultimate backend, over its documented HTTP interface.
Connecting and reading are CONFIRMED on hardware, 2026-09-04. Donald ran
Pool of Radiance on his own C64 Ultimate and pointed Wish at it: “I have Pool
of Radiance running on the C64 Ultimate. I have Wish running on this machine,
and it is able to successfully connect to the C64U and the automapper and
roster work.” That exercises configured, present, UltimateTarget.read
(the readmem call below) and every status-line and roster read the
automapper and the roster panel make. Backend.verified is True for that
reason – see #240 (Drive Pool of Radiance on the C64 Ultimate, so a VICE
reading can be checked against hardware).
Still not yet exercised, rather than “nobody has the hardware”, which is no
longer true of this project: UltimateTarget.write (writemem, below –
used by Heal Party and fast travel) and the X-Password header firmware
3.12+ may ask for. Donald’s machine runs firmware 3.14 and needed no
$POR_ULTIMATE_PASSWORD tonight, which says only that this device’s Web
Remote Control is not gated by one, not that the header itself works.
The interface is the REST API the Ultimate firmware serves over HTTP from 3.11 onwards (1541u-documentation.readthedocs.io, “REST API Calls”):
GET /v1/version – {“version”: “0.1”, …} GET /v1/info – product, firmware GET /v1/machine:readmem?address=<hex>&length=<n> – binary attachment POST /v1/machine:writemem?address=<hex> – binary body
readmem performs a DMA read on the cartridge bus, which is why this is a
backend at all: it is the only documented way to read the machine’s memory
without a resident stub. Firmware 3.12 and later may require a password, sent
as an X-Password header – not yet exercised.
Three things follow from the transport and are designed for rather than discovered late:
There is no discovery. Set
$POR_ULTIMATE(or$WISH_ULTIMATE) to the device’s host orhost:port; without it this backend does not probe and is never offered, so a machine with no Ultimate on the network sees no delay and no error.Latency is a network round trip, not a loopback socket, hence a slower default interval and hence batching – one read of
$4900-$64FFbeats sixty small ones. The four reads a fix costs are the budget worth watching.It stops the machine, and this was measured wrong for a day. The paragraph here used to say DMA does not stop the CPU. It does: the cartridge bus halts the 6510 for the length of the transfer, about 42 microseconds of fixed cost plus 1.1 microseconds a byte, so a 32 KB read holds it for about 36 milliseconds. Harmless while the game is drawing a map and not harmless during a disk load, where the KERNAL is bit-banging the serial bus with interrupts off and a halt in the middle of a byte loses the transfer – the drive keeps going, the KERNAL waits for a bit that has been and gone, and the game hangs where it stands (
#286 (Find out why the C64 Ultimate hangs),docs/197-duplicating-the-c64u-hang.md). SoBackend.disturbsis True, andUltimateTarget.halts_on_readtells the automapper to hold its tick while the drive is transferring (#375 (Wish has to work around the Ultimate freezing the C64 mid-load, which hangs the game while the automapper follows along)).
Known-unknown, stated rather than buried: party_fix (automap/target.py:145)
reads $D011, $D018 and $DD00 to find the screen, and it is not
Ultimate-specific code – it is the same free function VICE calls, taking any
backend’s read, so the only way it can fail here and not on VICE is a
cartridge-bus DMA read of one of those three registers coming back wrong.
~/c64u-reference.md already records three DMA reads of $D012 returning
E0, 48, FE on this exact machine – a moving raster counter, so DMA does
reach live VIC state. That makes party_fix succeeding on this machine
PROBABLE, not CONFIRMED: a working automapper is also exactly what the
$49C0 memory-copy fallback produces while somebody keeps walking, since that
fallback lags by only one move. The one-minute check that would settle it:
move one square, then stand still. The map catching up to the true square
within a poll or two means the status line is being read – the lag was only
default_interval_ms and the network round trip. The map staying one square
behind until the next move means party_fix fell back to the memory copy,
which never catches up on its own.
Functions
The device's address, if somebody has said where it is. |
|
|
Is a device answering where we were told to look? |
Classes
A |
- class wish.ultimate.UltimateTarget[source]¶
Bases:
objectA
Targetover the Ultimate’s REST API.readis confirmed on Donald’s own machine, 2026-09-04 – see the module docstring.`write` is confirmed too, 2026-09-05, and by a player rather than by a test: Donald pressed Level Up against the Ultimate and the character came back levelled. So the whole path – read the save off the hardware, edit it, write it back, and have the machine accept it – has been round-tripped once on real hardware.
`halts_on_read` is the one thing a caller has to design around. Every
readmemstops the 6510 for the length of the transfer, and a stop inside a disk load loses the byte the KERNAL was in the middle of receiving, so the game hangs.automap.busguardreads$DD00first and skips the tick while the drive is mid-transfer, which is what this attribute asks it to do (#375 (Wish has to work around the Ultimate freezing the C64 mid-load, which hangs the game while the automapper follows along)).- halts_on_read = True¶
hold the tick while the serial bus is busy, because a DMA read during a load hangs the machine.
- Type:
automap.busguard.HALTS_ON_READ