Skip to content

Add runtime VIC palette stream metadata - #871

Open
barryw wants to merge 13 commits into
GideonZ:masterfrom
barryw:feature/vic-stream-palette
Open

barryw wants to merge 13 commits into
GideonZ:masterfrom
barryw:feature/vic-stream-palette

Conversation

@barryw

@barryw barryw commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

VIC streams carry 4-bit color indices but no runtime palette, so clients cannot reproduce palette changes made by software such as Heartbeat. This adds an explicit palette=1 option to PUT /v1/streams/video:start and sends compact palette metadata to the active video destination without an FPGA change.

Existing clients remain unchanged: palette packets are opt-in, use the agreed 60-byte video-compatible pseudo-header, and ordinary video packets retain their current format. Firmware sends an atomic RGB/generation snapshot, repeats it once per second for UDP recovery, and coalesces rapid writes to at most one packet per 25 ms (20 ms rounded up to the 5 ms tick, so at most 40 packets/s). The opt-in is device-wide: the most recent video:start sets it.

Palette packets leave through the VIC stream's interface (SO_BINDTODEVICE) from its source port 53248, so palette and video arrive from one address and port for both unicast and multicast, including on a dual-homed Ultimate. The socket is connected to the destination, drained after each send, and closed when the stream stops.

One change reaches beyond the palette: the existing multicast test in data_streamer.cc matched only 232-239.x.x.x and now covers 224.0.0.0/4, so video/audio streams to groups in 224-231 are sent as multicast instead of through the gateway. It can move to its own PR if preferred.

Validation (Ultimate 64 Elite, FPGA 125, core 1.50):

  • U2 RISC-V, U2+, U2+L, U64/Elite and U64-II/Elite-II application targets compile on the CI-matched toolchains
  • default E2E gate 29/29; uci-targets palette 9/9 and palette-stream 10/10 (full logs in the comments)
  • 240 rapid changes gave 41 palette packets at 40.0/s alongside 3,908 video packets, none lost
  • unicast capture: palette and video both from the wired address, port 53248
  • OBS with Follow runtime VIC palette changes chrisgleissner/c64stream#131 applies each generation change live, with video and palette both from the wired address

Companion OBS plugin PR: chrisgleissner/c64stream#131

Refs #850

@chrisgleissner

Copy link
Copy Markdown
Collaborator

@barryw Wow, that was fast! Nice one!

@barryw

barryw commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@chrisgleissner I have a feeling that this is just the start of the conversation ;-)

Once @GideonZ gets ahold of it, I'm pretty certain I'll have some work to do lol

@GideonZ

GideonZ commented Sep 7, 2026

Copy link
Copy Markdown
Owner

I commented elsewhere about the format. I think you picked that up, right, @barryw ?

@barryw

barryw commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

I commented elsewhere about the format. I think you picked that up, right, @barryw ?

@GideonZ I did. I waited until you and @chrisgleissner chimed in before I started on anything, so what's here is based on that complete conversation. I'm working with @chrisgleissner on changes he's requested on his OBS plugin and that may require changes here. I'll let you guys know if this pr has to change.

@barryw

barryw commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@GideonZ @chrisgleissner Follow-up from the client review is in 9da3085: palette UDP now binds to the same source address as the FPGA VIC stream, and the firmware E2E enforces that source filter. The agreed 60-byte compatibility layout is unchanged. All five shipping ultimate application targets compile on the CI-matched Beast toolchains, and this PR's CI is green.

@barryw
barryw marked this pull request as draft September 7, 2026 21:38
@barryw

barryw commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@GideonZ @chrisgleissner Hardware validation on a dual-homed Ultimate 64 Elite found that forcing the software palette socket to the FPGA stream address breaks unicast. The FPGA VIC stream left Ethernet as 192.168.1.148, while the software UDP stack routed through Wi-Fi as 192.168.1.62; binding the software socket to .148 suppressed every unicast palette packet.

Follow-up 7949843c restores interface selection for unicast while retaining the source-bound multicast path. The OBS client already accepts only a discovery-verified alternate address for this case. Multicast passed all 23 hardware checks, including 240 rapid changes coalesced to 49 palette packets at 50.0 packets/s with 3,641 consecutive video packets. The corrected U64 image builds cleanly; unicast hardware revalidation is next after flashing it.

@barryw

barryw commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@GideonZ @chrisgleissner Final hardware validation is complete on an Ultimate 64 Elite running 7949843c.

  • Multicast: all 23 checks passed; 240 rapid changes coalesced to 49 palette packets at 50.7 packets/s with 3,648 consecutive video packets.
  • Unicast: all 23 checks passed; 240 rapid changes coalesced to 51 palette packets at 50.6 packets/s with 3,732 consecutive video packets.
  • OBS E2E: FPGA video arrived from wired 192.168.1.148 while palette metadata arrived from the Ultimate's Wi-Fi address 192.168.1.62. The plugin applied the initial generation and a live generation change, and the resulting color change was visible in the recording.

This confirms the corrected routing behavior: multicast remains source-bound, while unicast permits LwIP to use the working software interface. CI is green for the final commit.

}
stream_config_t *stream = &streams[streamID];
stream->enable = 0;
if (streamID == 0) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting a variable requires a critical section??

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No 😄. This was over-defensive. I added it to mirror the snapshot in sendVicPalette(), but a critical section around only this byte does not establish an atomic invariant while stream->enable is written outside it. The store is atomic on these targets, and the palette task can tolerate seeing the previous value for one iteration. I will remove this and the matching start-side wrapper.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 26d7af0: the single-variable critical sections are gone. What remains guards state that must change together; details in the PR conversation.

@GideonZ
GideonZ marked this pull request as ready for review September 8, 2026 08:48
@chrisgleissner chrisgleissner added 3.16 Targeted for release 3.16. enhancement New functionality or an improvement to existing behavior, including formats and developer tooling. labels Sep 9, 2026
@chrisgleissner
chrisgleissner changed the base branch from test-merge to master September 13, 2026 12:42
@chrisgleissner

Copy link
Copy Markdown
Collaborator

Hi @barryw and @GideonZ , can this be merged? As soon as it's merged, I'd like to also merge the nice PR that Barry kindly left against C64 Stream to fully support this exciting new feature. See chrisgleissner/c64stream#131

@barryw

barryw commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Hi @barryw and @GideonZ , can this be merged? As soon as it's merged, I'd like to also merge the nice PR that Barry kindly left against C64 Stream to fully support this exciting new feature. See chrisgleissner/c64stream#131

I've done a rebase and am running e2e now just to make sure we're still clean. We should be good, but this is not a trivial change.

@barryw
barryw force-pushed the feature/vic-stream-palette branch from 7949843 to b3bd458 Compare September 22, 2026 22:25
barryw added a commit to barryw/c64stream that referenced this pull request Sep 22, 2026
Firmware now sends runtime palette packets from the VIC stream's interface and
source port for unicast as well as multicast (GideonZ/1541ultimate#871).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@barryw
barryw force-pushed the feature/vic-stream-palette branch from b3bd458 to 26d7af0 Compare September 22, 2026 22:31
@barryw

barryw commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

@GideonZ @chrisgleissner I rebased this branch onto current master (ef9937a7) and pushed 26d7af06, which fixes what an adversarial review of the PR turned up. The wire format, the palette=1 opt-in and the generation semantics are unchanged, so C64 Stream needs no code change. Its spec paragraph on routing is updated in chrisgleissner/c64stream#131.

On the critical sections @GideonZ asked about in data_streamer.cc: fixed in 26d7af06. The single-variable ones are gone. What remains protects state that has to change together: one commit block in startStream (destination, enable and opt-in), the matching snapshot in the palette task, and the palette shadow plus its register writes in u64_config.cc.

Firmware

  • Failed start. A failed video:start (for example an unreachable host) used to overwrite the destination before failing. The FPGA then kept streaming to the old client while palette packets went to the new one. The start is now resolved into a copy, and the source, destination, enable and opt-in are committed together.
  • Opt-in carrier. The opt-in now travels as a mode bit. It used to ride in SubsysCommand::filename, which the task menu fills with the highlighted entry's name, so starting the VIC stream with a folder named 1 under the cursor opted in.
  • Validation order. palette is now checked before the debug stream is stopped, so a rejected request changes nothing.
  • Socket lifetime. The palette socket stayed open after the stream stopped and was never read. Eight datagrams sent to its port would pin every lwIP netbuf and cut off the other socket-API UDP receivers. It is now connected to the destination, drained after each send, and closed when the stream stops.
  • Source address. The socket is now bound to the VIC stream's interface (SO_BINDTODEVICE) and to its source port 53248. Palette and video therefore arrive from one address and port for unicast as well as multicast. This supersedes my earlier note that unicast palette packets leave through whatever route lwIP picks. The earlier attempt bound only the address, so lwIP still routed out over Wi-Fi with a source address that interface does not own, and dropped the packet. SO_BINDTODEVICE selects the interface itself, which lwIP honours on send.
  • Send failures. A failed send no longer tears the socket down, so a DHCP renew on the wired port cannot strand it.
  • Register writes. The palette registers are now written inside the same critical section as the shadow copy and the generation, so the VIC, GET_PALETTE and the stream cannot disagree when two writers race.
  • Packet rate. Packets are now at least 20 ms apart. With 5 ms ticks that means 25 ms, so at most 40 per second rather than about 50. The task also sleeps until woken while nobody has opted in.

Two changes reach beyond the palette:

  • Multicast range. The multicast test was (ip & 0xF8) == 0xE8, which matches only 232-239.x.x.x. A stream to a group such as 225.1.2.3 was therefore ARPed through the gateway and sent to the router's MAC. Both sites in data_streamer.cc now test for 224.0.0.0/4, so the existing video/audio path changes for groups in 224-231. debug_streamer_u2p.cc has the same test and I have left it alone; I'm happy to split this out into its own PR if you prefer.
  • Device-wide opt-in. The palette parameter's description now says what the implementation does: the opt-in belongs to the device and the most recent start sets it. It is not tracked per client.

E2E test

  • Own scenario. The stream checks now form their own uci-targets scenario, palette-stream, which runs after the command interface scenarios. It skips with the reason when the firmware lacks palette streams, when the VIC stream's interface has no link, or when the video arrives from a different address than -H. A machine that cannot stream to the test host no longer aborts the CTRL_CMD_LOAD_REU never returns and leaves the UCI wedged until a power cycle #740 matrix.
  • Reader thread. The stream is read on a background thread, so REST calls cannot overflow the receive buffer. Video is judged by stalls rather than by datagrams the host dropped.
  • Stronger checks.
    • The no-palette checks listen for 1.5 s and use video as the positive control.
    • Start, change and reset must each produce a palette packet within 0.3 s.
    • The repeat interval is checked.
    • A new check proves that a rejected palette=2 leaves a running debug stream alone.
  • Burst fixture. The burst fixture now starts only on GO=1, so the cleanup's GO=2 cannot release a parked burst into the restore.

Validation

Hardware: Ultimate 64 Elite, FPGA 125, core 1.50. The firmware was built from 9fb8c8d1 plus the working-tree changes now committed as 26d7af06 (which differs only in one reworded code comment), so /v1/info reports 9fb8c8d1.

Check Result
U2 RISC-V, U2+, U2+L, U64/Elite and U64-II/Elite-II application targets on the CI-matched toolchains all compile, no new warnings; app_space_test and openapi_check pass
Host unit tests (software/{api,filemanager,io/usb}/tests) 220 passed across 9 test programs
Default E2E gate, ./run-tests -H 192.168.1.148 29 of 29 suites passed, none retried, log below
./run-tests -H 192.168.1.148 -s uci-targets passed; palette 9 of 9, palette-stream 10 of 10, log below
Burst of 240 runtime palette changes 41 palette packets at 40.0/s; 3,908 video packets with none lost and a longest gap of 2 ms
Unicast capture, REST sent over Wi-Fi palette and video both from 192.168.1.148:53248
C64 Stream (chrisgleissner/c64stream#131) in OBS against this firmware, c64_host set to the wired and then the Wi-Fi address requests palette=1 and applies the new generation at the moment of each change; with the Wi-Fi address, discovery moves it to the wired interface

The gate runs against the wired address because the device has Ethernet connected, and the A/V streams leave from that port. The complete default-gate log follows in the next two comments, split only because of GitHub's comment size limit.

./run-tests -H 192.168.1.148 -s uci-targets

============================================================
Ultimate hardware test run
============================================================
     target:     192.168.1.148
     categories: e2e
     profile:    quick (default)
     modes:      overlay
     timeout:    30.0s
     on failure: continue
     health:    answers + health sweep, up to 3 attempts per suite
settings: capture 192.168.1.148 ... OK (197 settings in 18 stores, 0.509s)
machine: resets skip the RAM test ... OK (192.168.1.148: 'Disabled' -> 'Enabled', 0.042s)
the machine keeps its own bus resources ... OK (already set, 0.013s)

============================================================
E2E  uci-targets  (overlay)
============================================================
     uci-targets: health: ping=20ms rest=10ms ftp=41ms telnet=33ms ident=12ms dma=7ms heap=5236552B raster=22ms jiffy=32ms -> OK
[01] reset the C64 so the command interface starts idle ... OK (3.048s)
[02] read 'C64 and Cartridge Settings' ... OK (0.021s)
     current: {'Command Interface': 'Enabled', 'RAM Expansion Unit': 'Disabled', 'REU Preload Image': '/Usb0/preload.reu', 'REU Size': '2 MB', 'REU Preload Offset': '0 KB'}
[03] enable the Command Interface registers at $DF1B-$DF1F ... OK (0.053s)
[04] the command interface is idle once enabled ... OK (0.040s)
[05] transport: an empty command returns an empty reply ... OK (0.121s)
     <empty> -> 0.02s, data b'', status b''
[06] transport: unregistered target answers IDENTIFY ... OK (0.520s)
     0f 01 -> 0.06s, data b'NO TARGET', status b'00,OK'
[07] transport: unregistered target rejects anything else ... OK (0.686s)
     0f 7f -> 0.05s, data b'', status b'21,UNKNOWN COMMAND'
[08] transport: a no-reply command returns straight to Idle ... OK (0.133s)
[09] transport: pushing while a reply is pending sets and clears ERROR ... OK (0.893s)
[10] transport: the next command is not prefixed by leftover bytes ... OK (0.818s)
     04 28 00 -> 0.07s, data b'Ultimate 64 Elite', status b'00,OK'
[11] transport: ABORT releases a pending reply back to Idle ... OK (0.138s)
[12] control-target: IDENTIFY answers with the target name ... OK (0.855s)
     04 01 -> 0.05s, data b'CONTROL TARGET V1.1', status b'00,OK'
[13] control-target: an unimplemented command is reported as unknown ... OK (0.684s)
     04 7f -> 0.07s, data b'', status b'21,UNKNOWN COMMAND'
[14] control-target: LOAD_REU without a filename is rejected, not run ... OK (0.666s)
     04 08 -> 0.05s, data b'', status b'81,INVALID PARAMS'
[15] control-target: SAVE_REU without a filename is rejected, not run ... OK (0.648s)
     04 09 -> 0.06s, data b'', status b'81,INVALID PARAMS'
[16] control-target: GET_HWINFO rejects a device number it does not have ... OK (0.717s)
     04 28 02 -> 0.07s, data b'', status b'81,INVALID PARAMS'
     04 28 00 -> 0.06s, data b'Ultimate 64 Elite', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[17] palette: GET_PALETTE returns 16 RGB colors ... OK (1.683s)
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[18] palette: SET_PALETTE_COLOR changes only the requested color ... OK (2.054s)
     04 53 0f e8 e8 e8 -> 0.11s, data b'', status b'00,OK'
     04 51 -> 0.05s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xe8\xe8\xe8', status b'00,OK'
[19] palette: color index 16 is rejected ... OK (0.714s)
     04 53 10 00 00 00 -> 0.11s, data b'', status b'81,INVALID PARAMS'
[20] palette: SET_PALETTE replaces all 16 RGB colors ... OK (2.691s)
     04 52 0b 30 55 7a 9f c4 e9 0e 33 58 7d a2 c7 ec 11 36 5b 80 a5 ca ef 14 39 5e 83 a8 cd f2 17 3c 61 86 ab d0 f5 1a 3f 64 89 ae d3 f8 1d 42 67 8c b1 d6 -> 0.74s, data b'', status b'00,OK'
     04 51 -> 0.05s, data b'\x0b0Uz\x9f\xc4\xe9\x0e3X}\xa2\xc7\xec\x116[\x80\xa5\xca\xef\x149^\x83\xa8\xcd\xf2\x17<a\x86\xab\xd0\xf5\x1a?d\x89\xae\xd3\xf8\x1dBg\x8c\xb1\xd6', status b'00,OK'
[21] palette: short SET_PALETTE payload is rejected ... OK (1.331s)
     04 52 00 00 00 f7 f7 f7 8d 2f 34 6a d4 cd 98 35 a4 4c b4 42 2c 29 b1 ef ef 5d 98 4e 20 5b 38 00 d1 67 6d 4a 4a 4a 7b 7b 7b 9f ef 93 6d 6a ef b2 b2 -> 0.77s, data b'', status b'81,INVALID PARAMS'
[22] palette: GET_PALETTE rejects a payload ... OK (0.667s)
     04 51 00 -> 0.07s, data b'', status b'81,INVALID PARAMS'
[23] palette: RESET_PALETTE restores the built-in C64 colors ... OK (2.002s)
     04 54 -> 0.05s, data b'', status b'00,OK'
     04 51 -> 0.04s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[24] palette: RESET_PALETTE rejects a payload ... OK (0.665s)
     04 54 00 -> 0.07s, data b'', status b'81,INVALID PARAMS'
[25] palette: restore the original runtime palette ... OK (2.680s)
     04 52 00 00 00 f7 f7 f7 8d 2f 34 6a d4 cd 98 35 a4 4c b4 42 2c 29 b1 ef ef 5d 98 4e 20 5b 38 00 d1 67 6d 4a 4a 4a 7b 7b 7b 9f ef 93 6d 6a ef b2 b2 b2 -> 0.74s, data b'', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[26] issue-740-matrix: confirm /Temp/uci_e2e_absent.reu does not exist ... OK (0.012s)
[27] issue-740-matrix: put a 16384-byte image at /Temp/uci_e2e_present.reu ... OK (0.117s)
[28] issue-740-matrix: REU Enabled, image absent, name at offset 2 ... OK (3.146s)
     04 08 52 45 55 2e 49 4d 47 -> 0.15s, data b"\xff\xff\xff\xffREU LOAD: FAILED TO OPEN /TEMP/UCI_E2E_ABSENT.REU: FILE DOESN'T EXIST", status b'85,REU FILE CANNOT BE OPENED'
[29] issue-740-matrix: REU Enabled, image absent, name at offset 4 ... OK (3.234s)
     04 08 00 00 52 45 55 2e 49 4d 47 -> 0.19s, data b"\xff\xff\xff\xffREU LOAD: FAILED TO OPEN /TEMP/UCI_E2E_ABSENT.REU: FILE DOESN'T EXIST", status b'85,REU FILE CANNOT BE OPENED'
[30] issue-740-matrix: REU Disabled, image absent, name at offset 2 ... OK (0.949s)
     04 08 52 45 55 2e 49 4d 47 -> 0.16s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[31] issue-740-matrix: REU Disabled, image absent, name at offset 4 ... OK (0.978s)
     04 08 00 00 52 45 55 2e 49 4d 47 -> 0.19s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[32] issue-740-matrix: REU Enabled, image present, name at offset 2 ... OK (2.581s)
     04 08 52 45 55 2e 49 4d 47 -> 0.15s, data b'\x00@\x00\x00REU LOAD: LOADED 16384 BYTES TO $000000 FROM /TEMP/UCI_E2E_PRESENT.REU', status b'00,OK'
[33] issue-740-matrix: REU Enabled, image present, name at offset 4 ... OK (2.601s)
     04 08 00 00 52 45 55 2e 49 4d 47 -> 0.19s, data b'\x00@\x00\x00REU LOAD: LOADED 16384 BYTES TO $000000 FROM /TEMP/UCI_E2E_PRESENT.REU', status b'00,OK'
[34] save-reu-offset-past-end: put the preload offset at the end of a 128 KB REU ... OK (0.072s)
[35] save-reu-offset-past-end: SAVE_REU reports the offset and saves nothing ... OK (2.761s)
     04 09 52 45 55 2e 49 4d 47 -> 0.18s, data b'\xfd\xff\xff\xffREU SAVE: OFFSET 131072 >= REU SIZE 131072, SKIPPING', status b'86,REU OFFSET > SIZE. NOT SAVED'
[36] load-reu-disabled: disable the REU ... OK (0.025s)
[37] load-reu-disabled: leave text in the reply buffer from a previous command ... OK (0.812s)
     04 28 00 -> 0.08s, data b'Ultimate 64 Elite', status b'00,OK'
[38] load-reu-disabled: command reports the REU is disabled and replies with 4 bytes ... OK (0.904s)
     04 08 52 45 55 2e 49 4d 47 -> 0.15s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[39] save-reu-disabled: disable the REU ... OK (0.019s)
[40] save-reu-disabled: leave text in the reply buffer from a previous command ... OK (0.813s)
     04 28 00 -> 0.07s, data b'Ultimate 64 Elite', status b'00,OK'
[41] save-reu-disabled: command reports the REU is disabled and replies with 4 bytes ... OK (0.898s)
     04 09 52 45 55 2e 49 4d 47 -> 0.16s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[42] softiec-single-part-reply: SoftIEC target answers IDENTIFY ... OK (1.002s)
     05 01 -> 0.06s, data b'SOFTWARE IEC TARGET V1.0', status b'00,OK'
[43] softiec-single-part-reply: LOAD_SU on a missing file completes in one part ... OK (0.433s)
     05 10 00 00 00 00 00 00 4e 4f 53 55 43 48 46 49 4c 45 -> 0.30s, data b'', status b'\x01'
[44] softiec-single-part-reply: GET_FATNAME reply completes in one part ... OK (0.428s)
     05 22 02 23 -> 0.08s, data b'/buffer', status b'\x00'
[45] softiec-single-part-reply: an unimplemented SoftIEC command is reported as unknown ... OK (0.200s)
     05 7f -> 0.05s, data b'', status b'\x04'
[46] softiec-x00-name: put a P00 file, and a file named like one, on the RAM disk ... OK (0.112s)
[47] softiec-x00-name: a full path to a P00 file answers the name in its header and PRG ... OK (0.604s)
     05 23 2f 54 65 6d 70 2f 55 43 49 47 41 4d 45 2e 50 30 30 -> 0.28s, data b'\x01MY GAME', status b'\x00'
[48] softiec-x00-name: the same file without its directory answers the host name ... OK (0.692s)
     05 23 55 43 49 47 41 4d 45 2e 50 30 30 -> 0.21s, data b'\x00UCIGAME.P00', status b'\x00'
[49] softiec-x00-name: a file without the signature answers its host name ... OK (0.861s)
     05 23 2f 54 65 6d 70 2f 55 43 49 50 4c 41 49 4e 2e 50 30 30 -> 0.31s, data b'\x00UCIPLAIN.P00', status b'\x00'
     04 51 -> 0.05s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[50] palette-stream: a refused palette value leaves the debug stream running ... OK (0.964s)
[51] palette-stream: palette=0 sends no palette packets ... OK (1.505s)
[52] palette-stream: a palette change on an ordinary stream sends nothing ... OK (3.047s)
     04 53 0f e8 e8 e8 -> 0.43s, data b'', status b'00,OK'
[53] palette-stream: an opted-in start sends the current palette at once ... OK (0.158s)
[54] palette-stream: the palette repeats once a second ... OK (1.976s)
[55] palette-stream: a palette change is sent at once ... OK (2.485s)
     04 53 0f 17 17 17 -> 0.49s, data b'', status b'00,OK'
[56] palette-stream: rapid changes are coalesced without stopping the video ... OK (16.7s SLOW)
     04 51 -> 0.19s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
     04 51 -> 0.20s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B#h\x8b\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
     240 changes in 1.09 s -> 41 palette packets at 40.0/s; 3908 video packets, 0 lost on the way here, longest gap 2 ms
[57] palette-stream: RESET_PALETTE reaches an opted-in client at once ... OK (8.219s)
     04 54 -> 0.23s, data b'', status b'00,OK'
     04 51 -> 0.21s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[58] palette-stream: a later ordinary start is not opted in ... OK (1.617s)
[59] palette-stream: restore the original runtime palette ... OK (10.9s SLOW)
     04 52 00 00 00 f7 f7 f7 8d 2f 34 6a d4 cd 98 35 a4 4c b4 42 2c 29 b1 ef ef 5d 98 4e 20 5b 38 00 d1 67 6d 4a 4a 4a 7b 7b 7b 9f ef 93 6d 6a ef b2 b2 b2 -> 2.99s, data b'', status b'00,OK'
     04 51 -> 0.21s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[60] interface-usable-after: the control target still answers IDENTIFY ... OK (3.962s)
     04 01 -> 0.41s, data b'CONTROL TARGET V1.1', status b'00,OK'
     restored C64 and Cartridge Settings: {'Command Interface': 'Enabled', 'RAM Expansion Unit': 'Disabled', 'REU Preload Image': '/Usb0/preload.reu', 'REU Size': '2 MB', 'REU Preload Offset': '0 KB'}

--- summary
     transport: OK
     control-target: OK
     palette: OK
     issue-740-matrix: OK
     save-reu-offset-past-end: OK
     load-reu-disabled: OK
     save-reu-disabled: OK
     softiec-single-part-reply: OK
     softiec-x00-name: OK
     palette-stream: OK
     interface-usable-after: OK
     cleanup: OK
uci_targets_test: OK (60 checks, 108s)
uci-targets: OK (108s)

TEARDOWN (overlay): release input ... OK (0.018s)
TEARDOWN (overlay): close active menu UI ... OK (0.029s)
TEARDOWN (overlay): reset machine ... OK (0.046s)

settings: restore 192.168.1.148 ... OK (1 put back, 0.335s)
     192.168.1.148: C64 and Cartridge Settings / Fast Reset 'Enabled' -> 'Disabled'

============================================================
Summary
============================================================
     OK   e2e overlay    108s (1 ok)
     108s in suites, 2.500s in health checks, the UI-state gate and teardown
     111s total

OK  all 1 suite runs passed.
EXIT 0

@barryw

barryw commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Default E2E gate, ./run-tests -H 192.168.1.148 against the build described above, part 1 of 2.

Default gate log, part 1 of 2

============================================================
Ultimate hardware test run
============================================================
     target:     192.168.1.148
     categories: e2e
     profile:    quick (default)
     modes:      overlay
     timeout:    30.0s
     on failure: continue
     health:    answers + health sweep, up to 3 attempts per suite
settings: capture 192.168.1.148 ... OK (197 settings in 18 stores, 0.312s)
machine: resets skip the RAM test ... OK (192.168.1.148: 'Disabled' -> 'Enabled', 0.049s)
the machine keeps its own bus resources ... OK (already set, 0.013s)

============================================================
E2E  lint  (overlay)
============================================================
lint_test: OK (ruff 0.16.5 over tests, run-tests, 0.087s)
lint: OK (0.181s)

============================================================
E2E  registry  (overlay)
============================================================
[01] every suite in the tree is registered in run-tests ... OK (0.000s)
     77 suites under tests/lib, tests/e2e, tests/perf, tests/soak
[02] every registered path exists ... OK (0.000s)
     78 registered paths
[03] every argument template spells only tokens the runner knows ... OK (0.000s)
     tokens: @HOST@, @MODE@, @PASS@, @SOAKPROFILE@, @TIMEOUT@
[04] every registered argument is one the suite's parser accepts ... OK (0.473s)
     64 templates, against 3 shared argument helpers
[05] every sys.path line is the shared bootstrap ... OK (0.585s)
     one bootstrap, computed by walking up rather than by counting
[06] -H, -p and -t come from tests/lib/cli.py ... OK (0.663s)
     18 files keep their own, each with a reason
registry_test: OK (78 suites, 1.787s)
registry: OK (1.905s)

============================================================
E2E  transport-usage  (overlay)
============================================================
check_transport_usage: OK (140 files, 1.371s)
transport-usage: OK (1.482s)

============================================================
E2E  esp-depends  (overlay)
============================================================
[01] every cached ESP32 project contributes to the cache key ... OK (0.080s)
     watched: u64ctrl (24 files), wifi/raw_c3 (17 files), wifi/raw_u64 (17 files)
[02] a misspelt directory fails instead of silently hashing nothing ... OK (0.042s)
     wifi/raw_c3/main misspelt as wifi/raw_c3/main_does_not_exist: esp_depends.py exited 1
     a change to any ESP32 project invalidates the cached binaries CI would otherwise hand back unbuilt
esp_depends_test: OK (2 checks, 0.123s)
esp-depends: OK (0.233s)

============================================================
E2E  input-batching  (overlay)
============================================================
[01] a batch of short taps is limited by the event count ... OK (0.003s)
     64 events, 3852 bytes
[02] a batch of long taps is limited by the body size ... OK (0.003s)
     46 events, 4014 bytes
[03] the file browser's 64-keypress field clear fits its requests ... OK (0.001s)
     64 taps as [60, 4]
[04] one oversized event is passed on rather than dropped ... OK (0.000s)
[05] no events make no requests ... OK (0.000s)
input_batching_test: OK (5 checks, 0.039s)
input-batching: OK (0.132s)

============================================================
E2E  runner-policy  (overlay)
============================================================
[01] a bare target is its own C64-side computer ... OK (0.000s)
[02] a cartridge target names the computer it is plugged into ... OK (0.000s)
[03] keyboard input is the computer's, everything else the device's ... OK (0.000s)
[04] a malformed target is rejected before anything starts ... OK (0.000s)
[05] a target's output path is safe to use as a file name ... OK (0.000s)
[06] a cartridge named alone takes the computer the bench declares ... OK (0.000s)
[07] an undeclared cartridge is unchanged, and so is a spelt-out one ... OK (0.000s)
[08] a declaration that says nothing about this device is ignored ... OK (0.000s)
[09] a declaration that names this device and no computer is fatal ... OK (0.000s)
[10] two spellings of the same machines are one target ...      WARN u2 names the same machines as u2@c64u; running it once
OK (0.000s)
[11] the capture reads every store except the clock ... OK (0.000s)
[12] a run that changed nothing writes nothing ... OK (0.000s)
[13] only the items a suite changed are written back ... OK (0.000s)
[14] a value the device will not take back is reported, not hidden ... OK (0.000s)
[15] the profiles are ordered and cumulative ... OK (0.000s)
[16] a suite named with -s runs whatever the profile says ... OK (0.000s)
[17] the manual suites arrive with the deep profile, not before ... OK (0.000s)
[18] the transports widen with depth, and -m still overrides ... OK (0.000s)
[19] an unknown profile is refused, naming the real ones ... OK (0.000s)
[20] move_rows spends page keys before single steps ... OK (0.000s)
[21] the keys sent always add up to the rows asked for ... OK (0.000s)
[22] a zero move sends nothing at all ... OK (0.000s)
[23] the matrix agrees with what the runner selects ... OK (0.000s)
[24] a suite run is counted once per transport swept ... OK (0.000s)
[25] the static view names no duration and no check count ... OK (0.001s)
[26] measured counts come from the run records ... OK (0.004s)
[27] two targets conflict exactly when they share a machine ... OK (0.000s)
[28] a target does not start while a machine it needs is in use ... OK (0.000s)
[29] scheduling every target eventually runs each one exactly once ... OK (0.000s)
[30] a child repeats the parent's work against one target ... OK (0.000s)
[31] every runner option is forwarded to a child or excluded on purpose ... OK (0.000s)
[32] children write into a directory of their own ... OK (0.000s)
[33] concurrent children never tear each other's lines ... OK (0.061s)
[34] every line a child printed reaches the run's output ... OK (0.055s)
[35] the run's status is the worst of its children's ... OK (0.000s)
[36] the gate reads the menu from the device and types into the computer ... OK (0.000s)
[37] a single-device target still sends everything to one host ... OK (0.000s)
[38] only a cartridge target has a computer menu to get out of the way ... OK (0.000s)
[39] a menu that will not open is repaired by resetting the machine ... OK (0.000s)
[40] a menu that will not close is repaired by resetting the machine ... OK (0.000s)
[41] a launcher is descended by reading the cursor, not by pressing blind ... OK (0.000s)
[42] a cartridge target shuts the computer's menu before resetting ... OK (1.002s)
[43] a UI that the reset does not free is reported, not looped on ... OK (0.000s)
[44] no suite builds a REST URL from an unrouted host ... OK (0.000s)
     8 routing forms accepted, tests/lib/rest.py is where the rule lives
[45] a clean run exits 0 ... OK (0.000s)
[46] a suite that needed a second attempt exits EXIT_RETRIED ... OK (0.000s)
[47] a failed suite exits EXIT_SUITE_FAILED ... OK (0.000s)
[48] a recovery with no failure exits EXIT_RECOVERED ... OK (0.000s)
[49] a recovery outranks a retry ... OK (0.000s)
[50] a failure outranks a recovery ... OK (0.000s)
[51] a device that cannot be made healthy outranks a failure ... OK (0.000s)
[52] the statuses are a scale, in severity order ... OK (0.000s)
[53] a device that answers is never recovered ... OK (0.000s)
[54] recovery runs only once the ordinary wait has given up ... OK (0.019s)
     fixture: the device is unhealthy: it is not answering
[55] a recovery that does not bring the device back reports so ... OK (0.078s)
     fixture: the device is unhealthy: it is not answering
[56] a recovery command that exits non-zero is still followed by a probe ... OK (0.016s)
     fixture: the device is unhealthy: it is not answering
[57] a recovery command that hangs is bounded by --recover-timeout ... OK (0.206s)
     fixture: the device is unhealthy: it is not answering
[58] a recovery command the shell cannot find is a failed recovery ... OK (0.075s)
     fixture: the device is unhealthy: it is not answering
[59] no recovery command means no recovery ... OK (0.001s)
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: no --recover-command was given
[60] a suite gives up after --recover-max-per-suite recoveries ... OK (0.158s)
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: this suite has already used its 2 recoveries
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: this suite has already used its 2 recoveries
[61] the per-suite budget starts again at the next suite ... OK (0.073s)
     fixture: the device is unhealthy: it is not answering
[62] the run gives up after --recover-max-total recoveries ... OK (0.153s)
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: the run has already used its 2 recoveries
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: the run has already used its 2 recoveries
[63] the refusal says which ceiling was reached ... OK (0.083s)
     fixture: the device is unhealthy: it is not answering
[64] a healthy sweep after a failed suite recovers nothing ... OK (0.001s)
     fixture: health: rest=9ms -> OK
[65] a degraded but reachable device is recovered ... OK (0.013s)
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: after recovery, health: rest=9ms -> OK
[66] a device still degraded after recovering is reported, not retried ... OK (0.014s)
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: after recovery, health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: still unhealthy after recovering: it is degraded: ftp
[67] a degraded device is not recovered past its ceiling ... OK (0.013s)
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: after recovery, health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: still unhealthy after recovering: it is degraded: ftp
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: not recovering: the run has already used its 1 recoveries
[68] --no-health-check takes the sweep out of the decision ... OK (0.000s)
[69] --no-health-check still recovers a device that has gone ... OK (0.082s)
     fixture: the device is unhealthy: it is not answering
[70] -o names the output directory ... OK (0.000s)
[71] --output-dir names the same one ... OK (0.000s)
[72] -j is not accepted ... OK (0.000s)
[73] --jsonl-dir is not accepted ... OK (0.000s)
[74] the old spellings are gone from --help ... OK (0.000s)
[75] the examples use the option the parser accepts ... OK (0.000s)
[76] a child is told where to write with the same option ... OK (0.000s)
[77] consecutive resets collapse into one ... OK (0.000s)
[78] a read between two resets does not warrant the second ... OK (0.000s)
[79] a mutation through another object still warrants a reset ... OK (0.000s)
[80] a GET through another object does not warrant a reset ... OK (0.000s)
[81] a mutating call between two resets warrants the second ... OK (0.000s)
[82] force resets even when nothing has changed ... OK (0.000s)
[83] a suite told the device was just reset does not reset again ... OK (0.000s)
[84] a suite not told that still resets ... OK (0.000s)
[85] the default runs a failing suite exactly three times ... OK (0.106s)
[86] --attempts counts executions, so 2 runs it twice ... OK (0.070s)
[87] --attempts 1 and --no-retry both run it exactly once ... OK (0.066s)
[88] a healthy device does not stop a failure being retried ... OK (0.094s)
[89] recoveries do not buy extra executions ... OK (0.153s)
[90] a device that has died by the last attempt is still noticed ... OK (0.129s)
[91] a device that cannot be made healthy ends the run ... OK (0.036s)
[92] a suite killed by a signal says so instead of reading as a failure ... OK (0.039s)
fixture: OK (1.500s)
[93] a health sweep reaches the JSONL with a latency per check ... OK (0.000s)
[94] a suite record carries the profile, attempt and recoveries ... OK (0.000s)
[95] the run record agrees with the exit status ... OK (0.000s)
[96] a sweep with every check passing is healthy ... OK (0.000s)
[97] a failed check makes the sweep degraded and names it ... OK (0.000s)
[98] a skipped check never makes the sweep degraded ... OK (0.000s)
[99] the one-line summary carries every latency and the verdict ... OK (0.000s)
     the recovery command runs on a degraded or unreachable device, never on a suite that merely failed
runner_policy_test: OK (99 checks, 2.921s)
runner-policy: OK (3.060s)

============================================================
E2E  stale-gates  (overlay)
============================================================
[01] a check that passes under an assumed fix is reported stale ... OK (0.001s)
[02] a tag in a per-suite file is found; run.jsonl alone would miss it ... OK (0.001s)
[03] a check that still fails under the assumption is not stale ... OK (0.001s)
[04] one failure among repeats keeps the entry off the stale list ... OK (0.003s)
[05] with no assumption, the gate skips for real and tags nothing ... OK (0.001s)
[06] assuming every fix does not tag a machine that never needed one ... OK (0.000s)
[07] a run with nothing tagged reports no stale gates at all ... OK (0.000s)
stale_gates_test: OK (7 checks, 0.011s)
stale-gates: OK (0.121s)

============================================================
E2E  openapi-validator  (overlay)
============================================================
[01] the core_version field decides which document applies ... OK (0.000s)
[02] a declared profile is used instead of asking the device ... OK (0.000s)
[03] a profile with no document is refused ... OK (0.000s)
[04] a request path finds the call it belongs to ... OK (0.000s)
     8 paths, including the three configs depths
[05] a path the document does not describe matches nothing ... OK (0.000s)
[06] an answer that matches is accepted ... OK (0.453s)
[07] a body missing a required member is refused ... OK (0.000s)
[08] a member of the wrong type is refused ... OK (0.000s)
[09] a status the call does not declare is refused ... OK (0.000s)
[10] the password refusal every call shares is accepted ... OK (0.000s)
[11] JSON where the document promises bytes is refused ... OK (0.000s)
[12] a body that will not parse is refused ... OK (0.000s)
[13] an undescribed call is refused unless the device says so too ... OK (0.000s)
[14] a verb the call does not have is refused ... OK (0.000s)
[15] anything outside /v1 is left alone ... OK (0.000s)
[16] both documents load and describe their own product ... OK (0.157s)
     u2 55 operations, u64 59 operations
openapi_contract_test: OK (16 checks, 0.798s)
openapi-validator: OK (0.955s)

============================================================
E2E  navigation-keys  (overlay)
============================================================
[01] the two values, and an unknown one, resolve to a reading ... OK (0.001s)
[02] a letter is shifted only where the menu folds it back ... OK (0.000s)
[03] the menu cannot be made to read an uppercase letter under wasd ... OK (0.000s)
[04] the setting is read once per host ... OK (0.000s)
[05] a quick-seek prefix is spelt the way the machine will read it ... OK (0.000s)
[06] a name holding a cursor letter is still seeked for ... OK (0.000s)
[07] a popup's button key reaches the button rather than the cursor ... OK (0.000s)
[08] an overlay item is picked by the same spelling ... OK (0.000s)
[09] text typed into a field is never respelt ... OK (0.000s)
[10] a duration is read in every unit the suites use ... OK (0.000s)
[11] a duration that is not one is a usage error, not a traceback ... OK (0.000s)
[12] a duration has to be a length of time, so zero is refused ... OK (0.000s)
     a letter is shifted only where the firmware folds it back, so a field still receives what a suite typed
navigation_test: OK (12 checks, 0.062s)
navigation-keys: OK (0.197s)

============================================================
E2E  ui-backend-parse  (overlay)
============================================================
[01] a row marked by background colour is the selected one ... OK (0.000s)
[02] a blank row never wins, however it is coloured ... OK (0.000s)
[03] a screen with nothing marked says so, rather than guessing ... OK (0.000s)
[04] two entries are separated by the machine's own cursor colour ... OK (0.000s)
[05] a cursor that colours one field still marks its row ... OK (0.000s)
[06] a framed window is read instead of what it covers ... OK (0.000s)
[07] a window narrows what counts as a marking ... OK (0.000s)
[08] row_marks counts only the cells inside the window ... OK (0.000s)
[09] reverse video marks a row where no colour does ... OK (0.000s)
     the rules the parsers were written for, each as one screen
ui_backend_parse_test: OK (9 checks, 0.053s)
ui-backend-parse: OK (0.159s)

============================================================
E2E  telnet-drain  (overlay)
============================================================
[01] a capture that sent nothing waits only the quiet check ... OK (0.152s)
[02] a key known to draw nothing costs no more than that either ... OK (0.155s)
[03] a redraw that never starts gives up after the first-byte budget ... OK (1.081s)
[04] one burst ends the drain an idle gap after it ... OK (0.212s)
[05] a settled command waits through the pause and takes both bursts ... OK (6.785s)
[06] an ordinary command does not wait for a second burst ... OK (0.657s)
[07] a redraw arriving in pieces is drained as one ... OK (0.311s)
[08] a band header refuses a layout it cannot label ... OK (0.000s)
telnet_drain_test: OK (8 checks, 9.405s)
telnet-drain: OK (9.516s)

============================================================
E2E  ui-backend-smoke  (overlay)
============================================================
     ui-backend-smoke: health: ping=23ms rest=12ms ftp=55ms telnet=33ms ident=14ms dma=7ms heap=5293548B raster=23ms jiffy=24ms -> OK

--- Machine identification
[01] each machine is recognised from its product string ... OK (0.000s)
[02] only the C64 Ultimate opens on a launcher ... OK (0.000s)
[03] the C64 Ultimate searches CommoServe, the others Assembly 64 ... OK (0.000s)
[04] an unrecognised product is refused rather than guessed ... OK (0.000s)
[05] the product is fetched once and then kept ... OK (0.000s)
[06] the firmware version is kept when the caller has it ... OK (0.000s)
[07] every fix in the table names a behaviour and a machine that lacks it ... OK (0.000s)
[08] the table decides which machine skips a tagged check ... OK (0.000s)
[09] a fix no entry lists is one every machine has ... OK (0.000s)
[10] a skipped check names the fix and the machine in its reason ... OK (0.000s)
[11] skip_without_fix answers True only where the check cannot run ... OK (0.000s)
[12] an assumed fix runs the checks it gates, which is how a backport is found ... OK (0.000s)
[13] an assumption list is parsed as a list, and a typo is refused ... OK (0.000s)
--- OK (13 checks, 0.000s)

--- Overlay navigation planner
[14] a unique first letter jumps straight to the item ... OK (0.000s)
[15] a shared first letter extends the prefix rather than walking ... OK (0.000s)
[16] walking wins when the item is already next to the cursor ... OK (0.000s)
[17] the seek lands on the first match and the walk covers the rest ... OK (0.000s)
[18] a prefix never contains a space, which selects rather than seeks ... OK (0.000s)
[19] a plan is never longer than walking from the top ... OK (0.000s)
[20] an upward walk is planned when that is the shorter route ... OK (0.000s)
--- OK (7 checks, 0.000s)

--- Cursor row from the colour plane
[21] a background-marked cursor row is found ... OK (0.000s)
[22] a cursor row marked by foreground colour alone is found ... OK (0.000s)
[23] the first entry is found when it is the selected one ... OK (0.000s)
[24] a cursor that colours only the name field is still found ... OK (0.000s)
[25] a volume header in reverse video is not taken for a cursor ... OK (0.000s)
[26] a form marks a field, not a row, and the field is found ... OK (0.000s)
[27] reverse video still marks the cursor when nothing else does ... OK (0.000s)
[28] a listing of three or more entries says which colour is the cursor ... OK (0.000s)
[29] a background-marked screen teaches no foreground cursor colour ... OK (0.000s)
[30] two entries need the colour the machine was measured to mark with ... OK (0.000s)
--- OK (10 checks, 0.001s)

--- Cursor row under a framed window
[31] a plain listing reports no framed window ... OK (0.000s)
[32] a framed window's own cursor is read, not the browser's ... OK (0.000s)
[33] a cursor further down a framed window is read ... OK (0.000s)
[34] the menu's header rule is not taken for a framed window ... OK (0.000s)
[35] a titled window's cursor is read, not its title ... OK (0.000s)
[36] a window's blank rows never win the cursor colour ... OK (0.000s)
[37] a dialog inside a picker is read, not the picker ... OK (0.000s)
[38] an open window is what the cursor colour is measured on ... OK (0.000s)
[39] a form teaches no cursor colour at all ... OK (0.000s)
[40] a menu beside a highlighted row is read, not the row ... OK (0.000s)
[41] a window with rounded corners is a window too ... OK (0.000s)
[42] an untitled window keeps its first row ... OK (0.000s)
[43] a background-marked window is read on a background-marked browser ... OK (0.000s)
--- OK (13 checks, 0.002s)

--- REST backend, Interface Type = Overlay on HDMI
[44] REST/Overlay: connect, navigate, teardown ... OK (2.564s)
--- OK (1 checks, 2.564s)

--- Telnet backend
[45] Telnet: connect, navigate, teardown ... OK (2.025s)
--- OK (1 checks, 2.025s)

--- REST backend, Interface Type = Freeze
[46] REST/Freeze: connect, navigate, teardown ... OK (2.538s)
--- OK (1 checks, 2.539s)
ui_backend_smoke_test: OK (46 checks, 7.175s)
ui-backend-smoke: OK (7.265s)

============================================================
E2E  readmem-writemem  (overlay)
============================================================
     readmem-writemem: health: ping=33ms rest=10ms ftp=52ms telnet=33ms ident=14ms dma=7ms heap=5279796B raster=22ms jiffy=21ms -> OK
[01] reset C64 to a clean, stable state ... OK (0.042s)
[02] read User Interface Settings config ... OK (0.015s)
     current Interface Type: Freeze

--- bounds: readmem rejects out-of-range parameters before allocating, and max-size reads do not degrade the device
[03] readmem rejects zero length ... OK (0.010s)
[04] readmem rejects negative length ... OK (0.011s)
[05] readmem rejects length one past the 64KB cap ... OK (0.016s)
[06] readmem rejects length far past the cap ... OK (0.015s)
[07] readmem rejects length that overflows a long ... OK (0.010s)
[08] readmem rejects read running past $FFFF ... OK (0.011s)
[09] readmem rejects address above $FFFF ... OK (0.011s)
[10] readmem rejects negative address ... OK (0.010s)
[11] 8 back-to-back max-size reads ($0000, 65536 bytes) all succeed ... OK (1.587s)
[12] 25 unsupported machine:measure calls leave readmem working ... OK (0.510s)
--- OK (10 checks, 2.218s)

--- bad-address: readmem and writemem reject an address that is not valid hex
[13] readmem rejects address='ZZZZ' ... OK (0.014s)
[14] writemem PUT rejects address='ZZZZ' ... OK (0.011s)
[15] writemem POST rejects address='ZZZZ' ... OK (0.045s)
[16] readmem rejects address='0xZZZZ' ... OK (0.011s)
[17] writemem PUT rejects address='0xZZZZ' ... OK (0.011s)
[18] writemem POST rejects address='0xZZZZ' ... OK (0.040s)
[19] readmem rejects address='gggg' ... OK (0.011s)
[20] writemem PUT rejects address='gggg' ... OK (0.014s)
[21] writemem POST rejects address='gggg' ... OK (0.043s)
[22] readmem rejects address='-0' ... OK (0.011s)
[23] writemem PUT rejects address='-0' ... OK (0.012s)
[24] writemem POST rejects address='-0' ... OK (0.053s)
[25] readmem rejects address='+1' ... OK (0.012s)
[26] writemem PUT rejects address='+1' ... OK (0.011s)
[27] writemem POST rejects address='+1' ... OK (0.061s)
[28] readmem rejects address='12GG' ... OK (0.020s)
[29] writemem PUT rejects address='12GG' ... OK (0.012s)
[30] writemem POST rejects address='12GG' ... OK (0.065s)
--- OK (18 checks, 0.470s)

--- bad-debugreg: machine:debugreg rejects a value that is not two hex digits
[31] debugreg rejects value='ZZ' ... OK (0.029s)
[32] debugreg rejects value='0xZZ' ... OK (0.023s)
[33] debugreg rejects value='-0' ... OK (0.027s)
[34] debugreg rejects value='1G' ... OK (0.020s)
[35] debugreg rejects value='1FF' ... OK (0.033s)
[36] debugreg still takes a valid value without changing it ... OK (0.010s)
[37] switch to Overlay on HDMI to probe live background activity ... OK (0.011s)
[38] probe addresses that change on their own while the C64 runs ... OK (6.047s)
     6 address(es) treated as expected live background noise: $00A1, $00A2, $00CD, $00CF, $01F2, $0287
[39] set Interface Type = Freeze ... OK (0.011s)
[40] open menu (Freeze) ... OK (0.111s)
[41] every 4 KB of RAM answers DMA while the machine is frozen ... OK (1.457s)
[42] probe addresses that still change while the machine is frozen ... OK (0.888s)
     0 address(es) change on their own while frozen
[43] write full-range pattern (Freeze) ... OK (0.762s)
[44] read back full range (Freeze) ... OK (0.203s)
[45] menu still open after write+read (Freeze) ... OK (0.015s)
--- OK (15 checks, 9.653s)

--- selfcheck-freeze: write and read both happen in Freeze (screen RAM excluded, the menu redraws its entry rows while open)
     [selfcheck-freeze] ignored ROM mismatches (writes to real ROM are no-ops): 16330
     [selfcheck-freeze] screen ($0400-$07FF) mismatches: 907 (expected, menu owns this range while frozen)
     [selfcheck-freeze] ignored known-live-noise mismatches: 0
     [selfcheck-freeze] color RAM (low nibble) mismatches: 0
     [selfcheck-freeze] RAM mismatches: 0
[46] close menu (Freeze) ... OK (0.046s)
[47] set Interface Type = Overlay on HDMI ... OK (0.025s)
[48] open menu (Overlay on HDMI) ... OK (0.117s)
[49] pause C64 for exact round trip (Overlay on HDMI) ... OK (0.010s)
[50] write full-range pattern (Overlay on HDMI) ... OK (0.573s)
[51] read back full range (Overlay on HDMI) ... OK (0.205s)
[52] resume C64 after exact round trip (Overlay on HDMI) ... OK (0.011s)
[53] menu still open after write+read (Overlay on HDMI) ... OK (0.015s)
--- OK (8 checks, 1.034s)

--- selfcheck-overlay-on-hdmi: write and read both happen in Overlay on HDMI (screen RAM excluded, the menu redraws its entry rows while open)
     [selfcheck-overlay-on-hdmi] ignored ROM mismatches (writes to real ROM are no-ops): 0
     [selfcheck-overlay-on-hdmi] screen ($0400-$07FF) mismatches: 0 (expected, menu owns this range while frozen)
     [selfcheck-overlay-on-hdmi] ignored known-live-noise mismatches: 0
     [selfcheck-overlay-on-hdmi] color RAM (low nibble) mismatches: 0
     [selfcheck-overlay-on-hdmi] RAM mismatches: 0
[54] close menu (Overlay on HDMI) ... OK (0.041s)
[55] close menu for the screen round trip (Overlay on HDMI) ... OK (0.014s)
[56] pause C64 for the screen round trip (Overlay on HDMI) ... OK (0.010s)
[57] write and read back $0400-$07FF (Overlay on HDMI) ... OK (0.067s)
[58] resume C64 after the screen round trip (Overlay on HDMI) ... OK (0.020s)
--- OK (5 checks, 0.185s)

--- screen-round-trip-overlay-on-hdmi: screen RAM round-trips exactly with no menu open
     [screen-round-trip-overlay-on-hdmi] screen ($0400-$07FF) mismatches: 0 (expected 0)
[59] set Interface Type = Overlay on HDMI ... OK (0.011s)
[60] open menu (Overlay on HDMI) ... OK (0.036s)
[61] write full-range pattern (Overlay on HDMI) ... OK (0.495s)
[62] capture ground truth in Overlay on HDMI mode ... OK (0.194s)
[63] close menu (Overlay on HDMI) ... OK (0.038s)
[64] set Interface Type = Freeze ... OK (0.011s)
[65] open menu (Freeze) ... OK (0.176s)
[66] read full range (Freeze) ... OK (0.196s)
[67] menu still open after cross-mode read (Freeze) ... OK (0.017s)
--- OK (9 checks, 1.181s)

--- overlay-on-hdmi-write_freeze-read: bytes written while Overlay on HDMI must read back the same while Freeze, except screen RAM (menu-owned while frozen)
     [overlay-on-hdmi-write_freeze-read] screen ($0400-$07FF) mismatches: 996 (expected, menu owns this range while frozen)
     [overlay-on-hdmi-write_freeze-read] ignored known-live-noise mismatches: 0
     [overlay-on-hdmi-write_freeze-read] color RAM (low nibble) mismatches: 0
     [overlay-on-hdmi-write_freeze-read] RAM mismatches: 0
[68] close menu (Freeze) ... OK (0.125s)
[69] set Interface Type = Freeze ... OK (0.013s)
[70] open menu (Freeze) ... OK (0.105s)
[71] write full-range pattern (Freeze) ... OK (0.566s)
[72] capture ground truth in Freeze mode ... OK (0.194s)
[73] close menu (Freeze) ... OK (0.045s)
[74] set Interface Type = Overlay on HDMI ... OK (0.012s)
[75] open menu (Overlay on HDMI) ... OK (0.038s)
[76] read full range (Overlay on HDMI) ... OK (0.194s)
[77] menu still open after cross-mode read (Overlay on HDMI) ... OK (0.015s)
--- OK (10 checks, 1.346s)

--- freeze-write_overlay-on-hdmi-read: bytes written while Freeze must read back the same while Overlay on HDMI, except screen RAM (menu-owned while frozen)
     [freeze-write_overlay-on-hdmi-read] screen ($0400-$07FF) mismatches: 1020 (expected, menu owns this range while frozen)
     [freeze-write_overlay-on-hdmi-read] ignored known-live-noise mismatches: 0
     [freeze-write_overlay-on-hdmi-read] color RAM (low nibble) mismatches: 0
     [freeze-write_overlay-on-hdmi-read] RAM mismatches: 0
[78] close menu (Overlay on HDMI) ... OK (0.037s)
     restored Interface Type to 'Freeze'
--- OK (1 checks, 2.344s)

--- summary
     bounds: OK
     bad-address: OK
     bad-debugreg: OK
     selfcheck-freeze: OK
     selfcheck-overlay: OK
     screen-round-trip: OK
     overlay-to-freeze: OK
     freeze-to-overlay: OK
readmem_writemem_test: OK (78 checks, 24.6s)
readmem-writemem: OK (24.7s)

============================================================
E2E  menu-screen  (overlay)
============================================================
     menu-screen: health: ping=13ms rest=19ms ftp=40ms telnet=33ms ident=23ms dma=7ms heap=5282200B raster=22ms jiffy=58ms -> OK
[01] open menu ... OK (0.012s)
[02] GET menu_screen contract ... OK (0.017s)
[03] menu_screen renders a real menu ... OK (0.004s)
[04] close menu ... OK (0.012s)
[05] GET menu_screen unavailable ... OK (0.012s)
[06] GET menu_screen auth ... SKIP (--password not supplied, 0.000s)
menu_screen_test: OK (6 checks, 0.848s)
menu-screen: OK (0.955s)

============================================================
E2E  input  (overlay)
============================================================
     input: health: ping=9ms rest=10ms ftp=39ms telnet=34ms ident=14ms dma=7ms heap=5282200B raster=21ms jiffy=skip -> OK
[01] input snapshot has stable empty response shape ... OK (0.029s)
[02] POST accepts 64 event batch ... OK (0.056s)
[03] bad content-type is rejected without mutation ... OK (0.065s)
[04] missing JSON body is rejected without mutation ... OK (0.070s)
[05] malformed JSON is rejected without mutation ... OK (0.071s)
[06] unknown root field is rejected without mutation ... OK (0.060s)
[07] late invalid event keeps whole batch atomic ... OK (0.082s)
[08] joystick port 1 fire keeps Anykey buttons 2 and 3 released ... OK (0.320s)
[09] joystick port 1 fire2 lights only Anykey button 2, only on port 1 ... OK (0.326s)
[10] joystick port 1 fire3 lights only Anykey button 3, only on port 1 ... OK (0.346s)
[11] joystick port 2 fire keeps Anykey buttons 2 and 3 released ... OK (0.338s)
[12] joystick port 2 fire2 lights only Anykey button 2, only on port 2 ... OK (0.394s)
[13] joystick port 2 fire3 lights only Anykey button 3, only on port 2 ... OK (0.348s)
[14] joystick fire2 on one port and fire3 on the other stay independent ... OK (0.342s)
[15] joystick fire2/fire3 tap auto-releases the POT hardware state ... OK (0.423s)
[16] joystick port 1 up press is visible on CIA reads ... OK (0.109s)
[17] joystick port 1 all inputs and idempotent release are visible on CIA reads ... OK (0.116s)
[18] joystick port 2 diagonal and fire are visible on CIA reads ... OK (0.122s)
[19] joystick partial release is visible on CIA reads ... OK (0.117s)
[20] joystick fire2/fire3 round-trip through REST state and POT hardware ... OK (0.828s)
[21] joystick release in the same batch as a tap on the same input wins ... OK (0.033s)
[22] joystick release_all then press in same batch is visible on CIA reads ... OK (0.095s)
[23] joystick unusual combination is visible on CIA reads ... OK (0.101s)
[24] joystick tap does not release persistent input ... OK (0.322s)
[25] joystick tap auto releases ... OK (0.319s)
[26] invalid joystick batch does not mutate state ... OK (0.153s)
[27] joystick release_all clears both ports, including POT hardware ... OK (0.395s)
[28] machine reset clears keyboard and joystick REST state ... OK (3.118s)
[29] keyboard single-tap batch is consumed by BASIC in order ... OK (0.705s)
[30] keyboard 10 Hz mixed alphabet echo has no missed presses ... OK (2.826s)
[31] keyboard 20 Hz alternating ab echo has no missed presses ... OK (1.475s)
[32] keyboard 5 Hz alternating ab echo has no missed presses ... OK (0.774s)
[33] keyboard single letter reaches the live C64 matrix ... OK (0.162s)
[34] keyboard shifted pair reaches the live C64 matrix ... OK (0.216s)
[35] keyboard batch applies multiple presses atomically ... OK (0.248s)
[36] keyboard ordered batch and idempotent release ... OK (0.064s)
[37] keyboard release_all can be followed by press in same batch ... OK (0.076s)
[38] keyboard accepts eight simultaneous inputs ... OK (0.081s)
[39] keyboard tap does not release persistent key ... OK (0.175s)
[40] keyboard release_all clears state ... OK (0.062s)
[41] keyboard restore tap auto releases ... OK (0.255s)
[42] keyboard special-key taps snapshot correctly and auto release ... OK (2.558s)
[43] keyboard tap is visible in the live hardware snapshot and auto releases ... OK (0.272s)
[44] keyboard cursor-left tap is visible in the live hardware snapshot and auto releases ... OK (0.268s)
[45] keyboard tap batch drains through the live matrix path ... OK (1.453s)
[46] keyboard long repeated tap train drains fully without sticky state ... OK (4.531s)
[47] invalid keyboard batch does not mutate state ... OK (0.054s)
[48] mouse state is detached and empty after release_all ... OK (0.032s)
[49] a mouse button press attaches the mouse and is reported in order ... OK (0.115s)
[50] invalid mouse events are rejected with their error and change nothing ... OK (0.646s)
[51] a tap presses and releases, and a path is queued behind it ... OK (0.509s)
[52] the queue holds 1024 reports and refuses a batch beyond that with 429 ... OK (0.528s)
[53] a request over the JSON value limit is refused with a clear error ... OK (0.030s)
[54] release_all in a batch makes room for what follows it ... OK (5.238s)
[55] release_all detaches the mouse ... OK (0.063s)
[56] a machine reset detaches the mouse and drops its queue ... OK (3.136s)
input_test: OK (56 checks, 40.3s)
input: OK (40.5s)

============================================================

@barryw

barryw commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Default E2E gate, ./run-tests -H 192.168.1.148 against the build described above, part 2 of 2.

Default gate log, part 2 of 2
E2E  create-disk-image  (overlay)
============================================================
     create-disk-image: health: ping=12ms rest=11ms ftp=40ms telnet=33ms ident=19ms dma=7ms heap=5275072B raster=20ms jiffy=59ms -> OK

--- preconditions
[01] device reachable ... OK (0.013s)
[02] clear leftovers from an earlier run ... OK (0.112s)
--- OK (2 checks, 0.125s)

--- d64-35
[03] create d64 at /Temp/cdi_d64-35.d64 ... OK (0.050s)
[04] device still answers after create d64 ... OK (0.017s)
[05] /Temp/cdi_d64-35.d64 is 174848 bytes ... OK (0.013s)
--- OK (3 checks, 0.081s)

--- d64-40
[06] create d64 at /Temp/cdi_d64-40.d64 ... OK (0.053s)
[07] device still answers after create d64 ... OK (0.010s)
[08] /Temp/cdi_d64-40.d64 is 196608 bytes ... OK (0.013s)
--- OK (3 checks, 0.076s)

--- d71
[09] create d71 at /Temp/cdi_d71.d71 ... OK (0.103s)
[10] device still answers after create d71 ... OK (0.019s)
[11] /Temp/cdi_d71.d71 is 349696 bytes ... OK (0.013s)
--- OK (3 checks, 0.135s)

--- d81
[12] create d81 at /Temp/cdi_d81.d81 ... OK (0.154s)
[13] device still answers after create d81 ... OK (0.014s)
[14] /Temp/cdi_d81.d81 is 819200 bytes ... OK (0.017s)
--- OK (3 checks, 0.185s)

--- dnp-1
[15] create dnp at /Temp/cdi_dnp-1.dnp ... OK (0.085s)
[16] device still answers after create dnp ... OK (0.080s)
[17] /Temp/cdi_dnp-1.dnp is 65536 bytes ... OK (0.015s)
--- OK (3 checks, 0.180s)

--- dnp-16
[18] create dnp at /Temp/cdi_dnp-16.dnp ... OK (0.146s)
[19] device still answers after create dnp ... OK (0.010s)
[20] /Temp/cdi_dnp-16.dnp is 1048576 bytes ... OK (0.014s)
--- OK (3 checks, 0.170s)

--- repeat
[21] create d64 at /Temp/cdi_d64-35-repeat.d64 ... OK (0.059s)
[22] device still answers after create d64 ... OK (0.010s)
[23] /Temp/cdi_d64-35-repeat.d64 is 174848 bytes ... OK (0.014s)
--- OK (3 checks, 0.203s)
create_disk_image_test: OK (23 checks, 1.193s)
create-disk-image: OK (1.272s)

============================================================
E2E  d81-track-bounds  (overlay)
============================================================
     d81-track-bounds: health: ping=16ms rest=10ms ftp=41ms telnet=33ms ident=17ms dma=7ms heap=5275060B raster=23ms jiffy=29ms -> OK
[01] prepare 1581 and generated D81 fixtures ... OK (3.019s)

--- missing-track-81
[02] mount 80-track D81 on drive b ... OK (0.149s)
[03] device remains reachable after mount ... OK (0.017s)
[04] physical track 81 has the expected result ... OK (4.691s)
     tracks=80, job=$00, io=$00
--- OK (3 checks, 4.861s)

--- track-81
[05] mount 81-track D81 on drive b ... OK (0.146s)
[06] device remains reachable after mount ... OK (0.017s)
[07] physical track 81 has the expected result ... OK (4.711s)
     tracks=81, job=$00, io=$00
--- OK (3 checks, 4.874s)

--- oversized
[08] mount 85-track D81 on drive b ... OK (0.149s)
[09] device remains reachable after mount ... OK (0.016s)
[10] physical track 81 has the expected result ... OK (4.643s)
     tracks=85, job=$00, io=$00
--- OK (3 checks, 5.168s)
d81_track_bounds_test: OK (10 checks, 18.0s)
d81-track-bounds: OK (18.0s)

============================================================
E2E  rest-api-coverage  (overlay)
============================================================
     rest-api-coverage: health: ping=13ms rest=10ms ftp=49ms telnet=34ms ident=14ms dma=7ms heap=5276584B raster=21ms jiffy=67ms -> OK

--- coverage
[01] read the operation list ... OK (59 operations, from 2 OpenAPI document(s), 0.296s)
[02] every operation is exercised or excluded ... OK (58 exercised, 1 excluded, 0.000s)
[03] nothing is classified that the device does not serve ... OK (0.000s)
[04] every operation has a happy path somewhere ... OK (41 here, 9 elsewhere, 8 with none, 0.000s)
     not called: PUT /v1/machine:poweroff - turns the device off, and no test can turn it back on
     negative only: POST /v1/drives/{drive}:load_rom - as the PUT form
     negative only: POST /v1/runners:modplay - as the PUT form
     negative only: POST /v1/runners:run_crt - as the PUT form
     negative only: POST /v1/runners:sidplay - as the PUT form
     negative only: PUT /v1/drives/{drive}:load_rom - a wrong file leaves the drive with a broken ROM until the next reboot, and the tree has no drive ROM to load a good one from
     negative only: PUT /v1/runners:modplay - needs a MOD file and takes over the machine
     negative only: PUT /v1/runners:run_crt - needs a cartridge image and resets the C64
     negative only: PUT /v1/runners:sidplay - needs a SID file and takes over the machine
--- OK (4 checks, 0.296s)

--- preconditions
[05] device reachable ... OK (0.013s)
[06] record the state to restore ... OK (drive a: enabled=True type='1541', 9 settings, 0.167s)
--- OK (2 checks, 0.180s)

--- operations
     79 cases x3, concurrent, 3 workers
[07] GET /v1/version - returns a version x3 ... OK (0.000s)
[08] GET /v1/info - names product and firmware x3 ... OK (0.000s)
[09] GET /v1/info - reports the interface MAC addresses x3 ... OK (0.000s)
[10] GET /v1/info - names the commit it was built from x3 ... OK (0.000s)
[11] GET /v1/help - answers for a known command x3 ... OK (0.000s)
[12] GET /v1/help - refuses a missing command x3 ... OK (0.000s)
[13] GET /v1/configs - lists categories x3 ... OK (0.000s)
[14] GET /v1/configs/{category} - reads one category x3 ... OK (0.000s)
[15] GET /v1/configs/{category}/{item} - reads one item x3 ... OK (0.000s)
[16] GET /v1/configs/{category} - refuses an unknown category x3 ... OK (0.000s)
[17] PUT /v1/configs/{category}/{item} - writing the current value back is a no-op x3 ... OK (0.000s)
[18] PUT /v1/configs/{category}/{item}/{value} - the value-in-path form sets the same item x3 ... OK (0.000s)
[19] PUT /v1/configs/{category}/{item} - refuses an unknown category x3 ... OK (0.000s)
[20] POST /v1/configs - the JSON form sets an item x3 ... OK (0.000s)
[21] POST /v1/configs - refuses an unknown category x3 ... OK (0.000s)
[22] PUT /v1/configs:save_to_flash - save then load leaves the value x3 ... OK (0.000s)
[23] PUT /v1/configs:load_from_flash - covered by the save case above x3 ... OK (0.000s)
[24] PUT /v1/configs/{category}:save_to_flash - save then load leaves the value x3 ... OK (0.000s)
[25] PUT /v1/configs/{category}:load_from_flash - covered by the save case above x3 ... OK (0.000s)
[26] PUT /v1/configs/{category}:reset_to_default - resets one category and the values restore x3 ... OK (0.000s)
[27] PUT /v1/configs:reset_to_default - resets everything, needs --allow-global-reset x3 ... SKIP (resets every category at once; pass --allow-global-reset, 0.000s)
[28] GET /v1/files/{path}:info - describes /Temp x3 ... OK (0.000s)
[29] GET /v1/files/{path}:info - reports a missing path as absent x3 ... OK (0.000s)
[30] PUT /v1/files/{path}:create_d64 - refuses an unwritable path x3 ... OK (0.000s)
[31] PUT /v1/files/{path}:create_d71 - refuses an unwritable path x3 ... OK (0.000s)
[32] PUT /v1/files/{path}:create_d81 - refuses an unwritable path x3 ... OK (0.000s)
[33] PUT /v1/files/{path}:create_dnp - refuses an unwritable path x3 ... OK (0.000s)
[34] GET /v1/machine:readmem - returns the length asked for x3 ... OK (0.000s)
[35] GET /v1/machine:readmem - refuses a zero length x3 ... OK (0.000s)
[36] GET /v1/machine:heap - reports free within total x3 ... OK (0.000s)
[37] GET /v1/machine:measure - answers, 501 without bus measurement x3 ... OK (0.000s)
[38] GET /v1/machine:input - reports the input state x3 ... OK (0.000s)
[39] GET /v1/machine:debugreg - reads the register x3 ... OK (0.000s)
[40] PUT /v1/machine:debugreg - writing the held value back is a no-op x3 ... OK (0.000s)
[41] GET /v1/machine:menu_screen - answers 404 with no menu open x3 ... OK (0.000s)
[42] PUT /v1/machine:writemem - the byte written is the byte read back x3 ... OK (0.000s)
[43] POST /v1/machine:writemem - the uploaded block reads back x3 ... OK (0.000s)
[44] PUT /v1/machine:writemem - refuses a missing data argument x3 ... OK (0.000s)
[45] POST /v1/machine:input - refuses an empty event list x3 ... OK (0.000s)
[46] PUT /v1/machine:pause - pauses and resumes (status only) x3 ... OK (0.000s)
[47] PUT /v1/machine:resume - covered by the pause case above x3 ... OK (0.000s)
[48] PUT /v1/machine:menu_button - opens the menu and closes it again x3 ... OK (0.000s)
[49] PUT /v1/machine:reset - the C64 comes back to READY x3 ... OK (0.000s)
[50] PUT /v1/machine:reboot - the C64 comes back to READY x3 ... OK (0.000s)
[51] GET /v1/drives - lists both drive slots x3 ... OK (0.000s)
[52] PUT /v1/drives/{drive}:on - flips the drive and the listing agrees x3 ... OK (0.000s)
[53] PUT /v1/drives/{drive}:off - covered by the on/off case above x3 ... OK (0.000s)
[54] PUT /v1/drives/{drive}:mount - the listing shows the image x3 ... OK (0.000s)
[55] PUT /v1/drives/{drive}:unlink - keeps the disk, drops the file x3 ... OK (0.000s)
[56] PUT /v1/drives/{drive}:remove - clears the mounted image x3 ... OK (0.000s)
[57] POST /v1/drives/{drive}:mount - an uploaded image mounts x3 ... OK (0.000s)
[58] PUT /v1/drives/{drive}:set_mode - setting the current mode is a no-op x3 ... OK (0.000s)
[59] PUT /v1/drives/{drive}:reset - resets the drive (status only) x3 ... OK (0.000s)
[60] PUT /v1/drives/{drive}:reset - refuses an unknown drive letter x3 ... OK (0.000s)
[61] PUT /v1/drives/{drive}:mount - refuses a missing image x3 ... OK (0.000s)
[62] PUT /v1/drives/{drive}:load_rom - refuses a missing ROM file x3 ... OK (0.000s)
[63] POST /v1/drives/{drive}:load_rom - refuses a request with no body x3 ... OK (0.000s)
[64] PUT /v1/drives/{drive}:set_mode - refuses an unknown mode x3 ... OK (0.000s)
[65] PUT /v1/drives/{drive}:remove - refuses an unknown drive letter x3 ... OK (0.000s)
[66] PUT /v1/drives/{drive}:on - refuses an unknown drive letter x3 ... OK (0.000s)
[67] PUT /v1/drives/{drive}:off - refuses an unknown drive letter x3 ... OK (0.000s)
[68] PUT /v1/drives/{drive}:unlink - refuses an unknown drive letter x3 ... OK (0.000s)
[69] PUT /v1/runners:sidplay - refuses a missing file x3 ... OK (0.000s)
[70] POST /v1/runners:sidplay - refuses a request with no body x3 ... OK (0.000s)
[71] PUT /v1/runners:modplay - refuses a missing file x3 ... OK (0.000s)
[72] POST /v1/runners:modplay - refuses a request with no body x3 ... OK (0.000s)
[73] PUT /v1/runners:load_prg - refuses a missing file x3 ... OK (0.000s)
[74] POST /v1/runners:load_prg - refuses a request with no body x3 ... OK (0.000s)
[75] PUT /v1/runners:run_prg - refuses a missing file x3 ... OK (0.000s)
[76] POST /v1/runners:run_prg - refuses a request with no body x3 ... OK (0.000s)
[77] PUT /v1/runners:run_crt - refuses a missing file x3 ... OK (0.000s)
[78] POST /v1/runners:run_crt - refuses a request with no body x3 ... OK (0.000s)
[79] PUT /v1/streams/{stream}:start - starts and stops the debug stream x3 ... OK (0.000s)
[80] PUT /v1/streams/{stream}:stop - covered by the start case above x3 ... OK (0.000s)
[81] PUT /v1/streams/{stream}:start - refuses an unknown stream x3 ... OK (0.000s)
[82] PUT /v1/streams/{stream}:stop - refuses an unknown stream x3 ... OK (0.000s)
[83] protocol: an unknown route is 404 x3 ... OK (0.000s)
[84] protocol: a command asked for with the wrong method does not run x3 ... OK (0.000s)
[85] protocol: an unauthenticated read is refused x3 ... SKIP (no password is set on this device, 0.000s)
--- SKIP (79 checks, 25.2s)
rest_api_coverage_test: OK (85 checks, 25.7s)
rest-api-coverage: OK (25.8s)

============================================================
E2E  openapi-contract  (overlay)
============================================================
     openapi-contract: health: ping=20ms rest=12ms ftp=39ms telnet=32ms ident=24ms dma=7ms heap=5275972B raster=21ms jiffy=21ms -> OK
[01] u2 document is valid OpenAPI 3.1 ... OK (0.521s)
     44 paths, 33 schemas
[02] u64 document is valid OpenAPI 3.1 ... OK (0.543s)
     47 paths, 35 schemas
     Ultimate 64 Elite is described by the u64 document
[03] the device serves its own OpenAPI document ... OK (0.419s)
     148565 bytes, byte for byte the committed u64 document
[04] the device serves the API explorer ... OK (0.060s)
[05] a client generates from the u64 document ... OK (1.051s)
     package ultimate_64_rest_api_client
[06] the generated client reads the interface version ... OK (0.144s)
     version 0.1
[07] the generated client reads memory and agrees with the raw call ... OK (0.025s)
     $D020 reads fef6
[08] GET /v1/version matches the document ... OK (0.011s)
[09] GET /v1/info matches the document ... OK (0.016s)
[10] GET /v1/machine:heap matches the document ... OK (0.010s)
[11] GET /v1/drives matches the document ... OK (0.020s)
[12] GET /v1/configs matches the document ... OK (0.023s)
[13] GET /v1/configs/Drive%20A%20Settings matches the document ... OK (0.014s)
[14] GET /v1/configs/Drive%20A%20Settings/* matches the document ... OK (0.062s)
[15] GET /v1/machine:readmem matches the document ... OK (0.014s)
[16] GET /v1/machine:menu_screen matches the document ... OK (0.011s)
     HTTP 404
[17] a rejected call matches the document ... OK (0.010s)
[18] a refused call matches the document ... SKIP (no network password is configured on this device, 0.000s)
openapi_contract_test: OK (18 checks, 3.985s)
openapi-contract: OK (4.147s)

============================================================
E2E  browser-long-filename  (overlay)
============================================================
     browser-long-filename: health: ping=17ms rest=10ms ftp=50ms telnet=33ms ident=13ms dma=7ms heap=5275980B raster=20ms jiffy=54ms -> OK
[01] reset the machine to a clean starting state ... OK (0.560s)
[02] seed long-name fixture for rename ... OK (0.769s)
[03] browser rename on long filename ... OK (4.369s)
[04] reseed long-name fixture for mount ... OK (0.897s)
[05] browser mount on long filename ... OK (4.319s)
browser_long_filename_test: OK (5 checks, 11.4s)
browser-long-filename: OK (12.5s)

============================================================
E2E  ftp-client  (overlay)
============================================================
     ftp-client: health: ping=19ms rest=11ms ftp=39ms telnet=34ms ident=13ms dma=7ms heap=5274240B raster=22ms jiffy=28ms -> OK
     inferred --ftp-advertised-host 192.168.1.78
     controlled FTP server on 0.0.0.0:2121 (advertised 192.168.1.78)
[I 2026-09-22 17:46:33] concurrency model: async
[I 2026-09-22 17:46:33] masquerade (NAT) address: 192.168.1.78
[I 2026-09-22 17:46:33] passive ports: 30000->30019
[I 2026-09-22 17:46:33] >>> starting FTP server on 0.0.0.0:2121, pid=35825 <<<
     server root: /var/folders/52/5656d2050tz7nvc4b1m1tfqw0000gn/T/u64ftp-xl852l6v (marker E2E-1790113593)
[01] GET /v1/version reachable ... OK (0.1, 0.020s)
[02] menu closed -> menu_screen 404 ... OK (0.038s)
[03] menu_button opens menu ... OK (0.096s)
[04] menu_screen returns 2000-byte matrix ... OK (2000 bytes, 0.018s)
[05] input release_all works ... OK (0.015s)
[06] menu closes from anywhere ... OK (0.329s)
[07] remove any stale hosts left by a prior run ... OK (0 removed, 1.502s)

--- stage: smoke
[08] create FTP host 'E2EFTP3594392j' through the New Host form ... OK (7.825s)
[09] new alias appears under /ftp ... OK (1.667s)
[10] enter host: server sees login + TYPE I + LIST ... [I 2026-09-22 17:46:47] 192.168.1.62:52432-[] FTP session opened (connect)
[I 2026-09-22 17:46:47] 192.168.1.62:52432-[u64e2e] USER 'u64e2e' logged in.
OK (PASS,TYPE,LIST, 2.678s)
[11] root listing shows fixture entries ... OK (README,DIRA,SMALL, 0.014s)
[12] open README.TXT -> RETR (52 bytes) ... [I 2026-09-22 17:46:49] 192.168.1.62:52432-[u64e2e] RETR /private/var/folders/52/5656d2050tz7nvc4b1m1tfqw0000gn/T/u64ftp-xl852l6v/README.TXT completed=1 bytes=52 seconds=0.0
OK (52 bytes, 1.966s)
[13] remove host and confirm it disappears ... [I 2026-09-22 17:46:53] 192.168.1.62:52432-[u64e2e] FTP session closed (disconnect).
OK (5.490s)
--- OK (6 checks, 19.6s)

--- cleanup
     preserved: nothing

--- summary
     Phase    Operation              Result             Commands       Fixture/Notes
     ------------------------------------------------------------------------------
     smoke    create host            OK                                E2EFTP3594392j
     smoke    list host              OK                                E2EFTP3594392j
     smoke    enter/LIST             OK                 USER/PASS/TYPE E2EFTP3594392j
     smoke    root entries           OK                 LIST           README/DIRA/SMALL
     smoke    RETR README            OK                 RETR           README.TXT 52 bytes
     smoke    remove host            OK                                E2EFTP3594392j
     ------------------------------------------------------------------------------
     Total REST requests   : 122
     Total menu snapshots  : 56
     Total keyboard events : 87
     Total FTP commands    : 24
     Created host alias    : (removed)
     Remote root path      : /var/folders/52/5656d2050tz7nvc4b1m1tfqw0000gn/T/u64ftp-xl852l6v
     Cleanup status        : clean
Exception in thread Thread-1 (serve_forever):
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 1041, in _bootstrap_inner
    self.run()
    ~~~~~~~~^^
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 992, in run
    self._target(*self._args, **self._kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/barry/Library/Python/3.13/lib/python/site-packages/pyftpdlib/servers.py", line 254, in serve_forever
    self.ioloop.loop(timeout, blocking)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "/Users/barry/Library/Python/3.13/lib/python/site-packages/pyftpdlib/ioloop.py", line 376, in loop
    poll(timeout)
    ~~~~^^^^^^^^^
  File "/Users/barry/Library/Python/3.13/lib/python/site-packages/pyftpdlib/ioloop.py", line 741, in poll
    kevents = self._kqueue.control(
        None, _len(self.socket_map), timeout
    )
OSError: [Errno 9] Bad file descriptor
ftp_client_test: OK (6 operation(s), 23.6s)
ftp-client: OK (23.9s)

============================================================
E2E  prg-load-path-trim  (overlay)
============================================================
     prg-load-path-trim: health: ping=7ms rest=12ms ftp=45ms telnet=39ms ident=13ms dma=7ms heap=5275764B raster=21ms jiffy=63ms -> OK

--- Ultimate 64 PRG load path trim
     host: 192.168.1.148
     PUT file path: /Temp/rest-prg-path-trim-target-example.prg
     POST upload name: rest-prg-path-trim-upload-example.prg

--- 1. Capture Current Configuration
     Temp Auto Cleanup: Enabled
     Temp Subfolders: Enabled

--- 2. Configure Temp Settings
[01] set Temp Auto Cleanup to Enabled ... OK (0.013s)
[02] set Temp Subfolders to Enabled ... OK (0.014s)
--- OK (2 checks, 0.027s)

--- 3. Prepare PRG Fixture
[03] upload PUT fixture rest-prg-path-trim-target-example.prg ... OK (0.128s)
--- OK (1 checks, 0.129s)

--- 4. Validate PUT Endpoints
[04] exercise PUT runners:load_prg ... OK (0.447s)
     PUT load_prg boot-cart name: ...T-EXAMPLE
[05] exercise PUT runners:run_prg ... OK (1.763s)
     PUT run_prg boot-cart name: ...T-EXAMPLE
--- OK (2 checks, 2.374s)

--- 5. Validate POST Endpoints
[06] exercise POST runners:load_prg ... OK (0.438s)
     POST load_prg boot-cart name: ...D-EXAMPLE
[07] exercise POST runners:run_prg ... OK (1.796s)
     POST run_prg boot-cart name: ...D-EXAMPLE
--- OK (2 checks, 2.812s)
prg_load_path_trim_test: OK (7 checks, 5.986s)

--- restore User Interface Settings
[08] set Temp Auto Cleanup to Enabled ... OK (0.015s)
[09] set Temp Subfolders to Enabled ... OK (0.055s)
prg-load-path-trim: OK (6.890s)

============================================================
E2E  cfg-single-group  (overlay)
============================================================
     cfg-single-group: health: ping=8ms rest=16ms ftp=42ms telnet=33ms ident=15ms dma=17ms heap=5274288B raster=22ms jiffy=23ms -> OK
[01] load a one-group CFG file through the browser ... OK (5.190s)
cfg_single_group_test: OK (1 checks, 5.690s)
cfg-single-group: OK (6.225s)

============================================================
E2E  cfg-loader-log  (overlay)
============================================================
[01] an uninterrupted log gives the stores it names ... OK (0.000s)
[02] a log with no loader lines in it reports nothing ... OK (0.000s)
[03] a spliced effectuated record yields no store name ... OK (0.000s)
[04] both halves of a spliced effectuated record are reported ... OK (0.000s)
[05] a record appended to another task's line is reported, not read ... OK (0.000s)
[06] a spliced clean record yields no store name and is reported ... OK (0.000s)
cfg_loader_log_test: OK (6 checks, 0.049s)
cfg-loader-log: OK (0.132s)

============================================================
E2E  printer  (overlay)
============================================================
     printer: health: ping=17ms rest=10ms ftp=48ms telnet=33ms ident=13ms dma=7ms heap=5277336B raster=32ms jiffy=25ms -> OK
[01] REST reachable at 192.168.1.148 ... OK (0.012s)
[02] reset before run ... OK (0.088s)
[03] load printer_e2e.prg ... OK (914 bytes, 0.002s)
[04] capture original Printer Settings ... OK (0.149s)
[05] print epson/bitmap: apply settings + run PRG ... OK (phase=printer closed heartbeat=4, 2.676s)
[06] print epson/bitmap: Flush/Eject via on-screen menu ... OK (3.984s)

--- restoring original Printer Settings
     IEC printer: Disabled
     Bus ID: 4
     Output file: /Usb0/printer
     Output type: PNG B&W
     Ink density: Medium
     Page top margin (default is 5): 5
     Page height (default is 60): 60
     Emulation: Commodore MPS
     Commodore charset: USA/UK
     Epson charset: Basic
     IBM table 2: International 1

--- summary
     epson      bitmap PASS_NO_VERIFY       /USB1/printer/e2e-eb6215
printer_test: OK (1 combination(s), 8.163s)
printer: OK (8.284s)

============================================================
E2E  usb-mouse  (overlay)
============================================================
     usb-mouse: health: ping=31ms rest=18ms ftp=47ms telnet=33ms ident=16ms dma=7ms heap=5272808B raster=23ms jiffy=21ms -> OK
     saved settings: Mouse Mode='Mouse', Mouse Sensitivity=8, Mouse Acceleration='Off', Mouse Wheel Sensitivity=8, Mouse Wheel Direction='Normal', Joystick Swapper='Normal', Menu Mouse Navigation='Enabled', System Mode='NTSC'
     quick profile: move, precision, buttons, wheel-micromys, cursor-mode
[01] moves [(30, 20)] land at (30, 20) ... OK (0.855s)
     x=30 y=20 step x(0, 30) y(0, 20) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$54/$70 lines=$00 frames=27 moved=1
[02] moves [(-80, -65)] land at (-80, -65) ... OK (0.617s)
     x=-80 y=-65 step x(-31, 0) y(-31, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$78/$1A lines=$00 frames=31 moved=3
[03] moves [(63, -63), (-20, 40)] land at (43, -23) ... OK (0.640s)
     x=43 y=-23 step x(-20, 31) y(-31, 31) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$6E/$46 lines=$00 frames=32 moved=4
[04] 120 reports of 1,-1 move a frame each ... OK (3.240s)
     x=120 y=-120 step x(0, 1) y(-1, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$08/$08 lines=$00 frames=188 moved=120
[05] 120 reports of -2,2 move a frame each ... OK (3.240s)
     x=-240 y=240 step x(-2, 0) y(0, 2) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$38/$38 lines=$00 frames=187 moved=120
[06] all 8 button states ... OK (1.597s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=4 middle=2 right=1] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=89 moved=0
[07] a REST tap of each button, and of all three, is one press each ... OK (0.973s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=2 middle=2 right=2] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=56 moved=0
[08] sensitivity 3: 2 up and 1 down detents, slowly, give 3 pulses each and leave the position ... OK (1.983s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=6 down=3 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=117 moved=0
[09] 10 detents as fast as the host polls queue and all pulse ... OK (1.329s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=10 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=77 moved=0
[10] Cursor, sensitivity 2: wheel -2/0 types cursor keys {'up': 0, 'down': 8, 'left': 0, 'right': 0} ... OK (1.143s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[down=8] keys held=0 [0.7=8] pot=$18/$18 lines=$00 frames=67 moved=0
[11] Cursor, sensitivity 2: wheel 0/1 types cursor keys {'up': 0, 'down': 0, 'left': 3, 'right': 0} ... OK (0.777s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[left=3] keys held=0 [0.2=3 6.4=3] pot=$18/$18 lines=$00 frames=45 moved=0
[12] Cursor: [(8, 0)] types {'right': 2} and leaves the pointer ... OK (0.618s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[right=2] keys held=0 [0.2=2] pot=$18/$18 lines=$00 frames=35 moved=0
[13] Cursor: [(-12, 0)] types {'left': 3} and leaves the pointer ... OK (0.545s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[left=3] keys held=0 [0.2=3 6.4=3] pot=$18/$18 lines=$00 frames=32 moved=0
[14] Cursor: [(0, 8)] types {'down': 2} and leaves the pointer ... OK (0.520s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[down=2] keys held=0 [0.7=2] pot=$18/$18 lines=$00 frames=30 moved=0
[15] Cursor: [(0, -2)] types nothing and leaves the pointer ... OK (0.471s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=27 moved=0
[16] Cursor: [(0, -2), (0, -2)] types {'up': 1} and leaves the pointer ... OK (0.565s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[up=1] keys held=0 [0.7=1 6.4=1] pot=$18/$18 lines=$00 frames=33 moved=0
[17] Cursor: [(1, 0), (1, 0), (1, 0)] types nothing and leaves the pointer ... OK (0.613s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=36 moved=0
[18] Cursor: [(1, 0), (1, 0), (1, 0), (1, 0)] types {'right': 1} and leaves the pointer ... OK (0.662s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[right=1] keys held=0 [0.2=1] pot=$18/$18 lines=$00 frames=38 moved=0
[19] Cursor: motion far faster than the keys can be typed stops soon after the mouse ... OK (1.578s)
     keys kept coming for 0.94s after the mouse stopped: x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[right=19] keys held=0 [0.2=19] pot=$18/$18 lines=$00 frames=94 moved=0
[20] Cursor: turning the other way drops the keys still waiting ... OK (0.779s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[left=1 right=5] keys held=0 [0.2=6 6.4=1] pot=$18/$18 lines=$00 frames=45 moved=0
mouse_test: OK (20 checks, 25.6s)
usb-mouse: OK (25.7s)

============================================================
E2E  ftp-server  (overlay)
============================================================
     ftp-server: health: ping=9ms rest=11ms ftp=40ms telnet=33ms ident=13ms dma=6ms heap=5271264B raster=20ms jiffy=57ms -> OK

--- a name the listing reports can address the file it names
[01] a 40-character name is listed unchanged ... OK (0.050s)
[02] that file is removed by the name the listing reported ... OK (0.061s)
[03] a 100-character name is listed unchanged ... OK (0.056s)
     100 characters survived the listing
[04] SIZE answers for the long name the listing reported ... OK (0.042s)
[05] that file is removed by the name the listing reported ... OK (0.058s)
--- OK (5 checks, 0.342s)
ftp_server_test: OK (5 checks, 0.352s)
ftp-server: OK (0.435s)

============================================================
E2E  assembly64  (overlay)
============================================================
     assembly64: health: ping=20ms rest=11ms ftp=54ms telnet=33ms ident=13ms dma=7ms heap=5269748B raster=29ms jiffy=23ms -> OK
     this machine searches Assembly 64, from its task menu

--- the form opens from the menu and closes again
[01] open the Assembly 64 Query Form ... OK (0.733s)
[02] the form leaves cleanly with RUN/STOP ... OK (0.343s)
--- OK (2 checks, 1.165s)

--- a real query is sent to Assembly 64
[03] open the form and enter the first field ... OK (0.832s)
     the query term goes in 'Name:', the form's first field
[04] the typed term lands in the first field ... OK (0.702s)
[05] the service answers and the results match what was asked for ... OK (1.039s)
     20 result rows mention 'turrican'
--- OK (3 checks, 3.005s)

--- dropdown selections preserve the query form (#863)
[06] the form draws fields, and at least one of them is a dropdown ... OK (7.886s)
     13 field(s) drawn: Name:, Group:, Handle:, Event:, Repo:, Category:, Subcat:, Rating:, Type:, Date:, Latest:, Sort:, Order:
     4 free text: Name:, Group:, Handle:, Event:
     9 dropdown: Repo:, Category:, Subcat:, Rating:, Type:, Date:, Latest:, Sort:, Order:
[07] populate the free-text fields ... OK (3.706s)
[08] Repo: +/- preserves existing fields ... OK (0.704s)
[09] cancelling Repo: preserves existing fields ... OK (0.496s)
[10] confirming Repo: preserves its value and all other fields ... OK (2.032s)
[11] Category: +/- preserves existing fields ... OK (0.863s)
[12] cancelling Category: preserves existing fields ... OK (0.569s)
[13] confirming Category: preserves its value and all other fields ... OK (2.214s)
[14] Subcat: +/- preserves existing fields ... OK (1.063s)
[15] cancelling Subcat: preserves existing fields ... OK (0.556s)
[16] confirming Subcat: preserves its value and all other fields ... OK (2.060s)
[17] Rating: +/- preserves existing fields ... OK (0.967s)
[18] cancelling Rating: preserves existing fields ... OK (0.593s)
[19] confirming Rating: preserves its value and all other fields ... OK (2.069s)
[20] Type: +/- preserves existing fields ... OK (0.998s)
[21] cancelling Type: preserves existing fields ... OK (0.562s)
[22] confirming Type: preserves its value and all other fields ... OK (1.969s)
[23] Date: +/- preserves existing fields ... OK (0.977s)
[24] cancelling Date: preserves existing fields ... OK (0.567s)
[25] confirming Date: preserves its value and all other fields ... OK (2.128s)
[26] Latest: +/- preserves existing fields ... OK (0.902s)
[27] cancelling Latest: preserves existing fields ... OK (0.556s)
[28] confirming Latest: preserves its value and all other fields ... OK (1.955s)
[29] Sort: +/- preserves existing fields ... OK (1.029s)
[30] cancelling Sort: preserves existing fields ... OK (0.517s)
[31] confirming Sort: preserves its value and all other fields ... OK (2.025s)
[32] Order: +/- preserves existing fields ... OK (1.248s)
[33] cancelling Order: preserves existing fields ... OK (0.554s)
[34] confirming Order: preserves its value and all other fields ... OK (2.031s)
--- OK (29 checks, 44.2s)

--- the menu button must work from inside the edit field
[35] open the form and enter the edit field ... OK (0.761s)
[36] the menu button closes the menu from inside the field ... OK (0.089s)
[37] the menu opens again, so the UI task left string_edit ... OK (0.029s)
--- OK (3 checks, 1.305s)

--- an aborted edit must not wedge the form
[38] open the form, type into a field, then abort ... OK (1.438s)
[39] the form is still usable after the abort ... OK (0.023s)
--- OK (2 checks, 1.882s)

--- over-long and empty input
[40] type more than the field accepts ... OK (2.037s)
[41] an empty query is refused rather than sent ... OK (1.692s)
[42] the warning is dismissed and the form is still usable ... OK (0.217s)
--- OK (3 checks, 4.412s)

--- a user mashing keys while the form is busy
[43] submit a query and hammer keys while it runs ... OK (4.359s)
[44] the device is still answering after the mashing ... OK (0.011s)
--- OK (2 checks, 4.803s)

--- opening and abandoning the form repeatedly
[45] open and abandon the form three times ... OK (2.668s)
[46] the menu button closes and reopens the form three times ... OK (3.171s)
--- OK (2 checks, 6.042s)
assembly64_test: OK (46 checks, 67.2s)
assembly64: OK (67.3s)

============================================================
E2E  freezer-audio  (overlay)
============================================================
     freezer-audio: health: ping=8ms rest=10ms ftp=46ms telnet=33ms ident=13ms dma=7ms heap=5230364B raster=21ms jiffy=20ms -> OK
[01] entering the freezer menu is silent on a silent machine ... OK (0.774s)
     entry peak RMS 0.001
[02] a SID tune reaches the audio stream ... OK (2.018s)
     baseline RMS 0.106
[03] entering the freezer menu is silent while a tune plays ... OK (0.227s)
     entry peak RMS 0.102 against a 0.106 tune
[04] the freezer menu silences the machine ... OK (0.402s)
     frozen RMS 0.000, under the 0.011 bound
[05] closing the freezer menu gives the sound back ... OK (1.413s)
     resumed RMS 0.106
[06] playing a SID from the freezer menu gives the sound back ... OK (2.245s)
     relaunched RMS 0.107
freezer_audio_test: OK (6 checks, 8.466s)
freezer-audio: OK (8.557s)

TEARDOWN (overlay): release input ... OK (0.076s)
TEARDOWN (overlay): close active menu UI ... OK (0.116s)
TEARDOWN (overlay): reset machine ... OK (0.297s)

settings: restore 192.168.1.148 ... OK (1 put back, 1.219s)
     192.168.1.148: C64 and Cartridge Settings / Fast Reset 'Enabled' -> 'Disabled'

============================================================
Summary
============================================================
     OK   e2e overlay    300s (29 ok)
     slowest: assembly64 67.3s, input 40.5s, rest-api-coverage 25.8s
     300s in suites, 20.3s in health checks, the UI-state gate and teardown
     321s total

OK  all 29 suite runs passed.
EXIT 0

@chrisgleissner

chrisgleissner commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks for having added the full default E2E gate logs @barryw , and all the other changes and fixes - much appreciated! Stellar work!

Did you check with @GideonZ , is he OK to have this merged, and shall this go into 3.15b or 3.16?

We currently don't have the means to distinguish both - everything that currently goes into master is scheduled for 3.15b as per my understanding. If it's a backwards compatible change, I don't see the harm in releasing it with 3.15b.

@barryw

barryw commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @chrisgleissner. No, I haven't had an explicit go-ahead from @GideonZ yet. His one review point (the single-variable critical sections) was addressed in 26d7af0, and he hasn't looked again since.

On compatibility: existing clients see the same packets. Palette packets are only sent after video:start with palette=1, and ordinary video packets keep their format. Inside the firmware a few things apply to every U64 regardless of the opt-in: a small "VIC Palette" task that sleeps until woken, the palette register writes now sitting inside a critical section, and a failed stream start no longer overwriting the running stream's destination. The one change to existing wire behaviour is the multicast range fix in data_streamer.cc (224.0.0.0/4 instead of only 232–239).

That leaves two options: merge now so it ships in 3.15b, or hold it until master opens for 3.16. Either way I can split the multicast fix into its own PR if you'd rather decide on that separately. My preference is 3.15b, since the client side is compatible, but that's a judgement call. @GideonZ, your call.

@GideonZ

GideonZ commented Sep 28, 2026

Copy link
Copy Markdown
Owner

I hope to be up and running again within some weeks, @barryw

@chrisgleissner

Copy link
Copy Markdown
Collaborator

Hi @barryw , there is now a merge conflict. Thanks for fixing.

IMHO It would be nice to add this to 3.15b instead of waiting for 3.16, but that is of course @GideonZ 's call. Reasoning is that it's backwards compatible.

@barryw
barryw force-pushed the feature/vic-stream-palette branch from 26d7af0 to 30afb19 Compare October 1, 2026 19:05
@barryw

barryw commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@chrisgleissner Rebased onto master 2c60bd8; it's now 30afb19. The only conflict was in uci_targets_test.py, where master's new softiec-setting-modes scenario and this PR's palette-stream were both added at the same place in the scenario list and the run order. I kept both, with palette-stream after softiec-setting-modes so it still runs after the other command interface scenarios.

Retested on the rebased build (Ultimate 64 Elite, FPGA 125, core 1.50): default gate 31/31, and uci-targets with palette 9/9 and palette-stream 10/10. Logs below. Thanks for flagging the conflict.

@barryw

barryw commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Default E2E gate, ./run-tests -H 192.168.1.148, against the rebased branch (30afb19 on master 2c60bd8; Ultimate 64 Elite, FPGA 125, core 1.50). Part 1 of 2.

Default gate log, part 1 of 2

============================================================
Ultimate hardware test run
============================================================
     target:     192.168.1.148
     categories: e2e
     profile:    quick (default)
     modes:      overlay
     timeout:    30.0s
     on failure: continue
     health:    answers + health sweep, up to 3 attempts per suite
settings: capture 192.168.1.148 ... OK (197 settings in 18 stores, 0.242s)
machine: resets skip the RAM test ... OK (already set, 0.013s)
the machine keeps its own bus resources ... OK (already set, 0.011s)

============================================================
E2E  lint  (overlay)
============================================================
lint_test: OK (ruff 0.16.5 over tests, run-tests, 0.347s)
lint: OK (0.448s)

============================================================
E2E  registry  (overlay)
============================================================
[01] every suite in the tree is registered in run-tests ... OK (0.000s)
     83 suites under tests/lib, tests/e2e, tests/perf, tests/soak
[02] every registered path exists ... OK (0.000s)
     84 registered paths
[03] every argument template spells only tokens the runner knows ... OK (0.000s)
     tokens: @HOST@, @KERNAL@, @MODE@, @PASS@, @SOAKPROFILE@, @TIMEOUT@
[04] every registered argument is one the suite's parser accepts ... OK (0.425s)
     68 templates, against 3 shared argument helpers
[05] every sys.path line is the shared bootstrap ... OK (0.919s)
     one bootstrap, computed by walking up rather than by counting
[06] -H, -p and -t come from tests/lib/cli.py ... OK (0.831s)
     18 files keep their own, each with a reason
registry_test: OK (84 suites, 2.281s)
registry: OK (2.374s)

============================================================
E2E  transport-usage  (overlay)
============================================================
check_transport_usage: OK (147 files, 1.554s)
transport-usage: OK (1.655s)

============================================================
E2E  esp-depends  (overlay)
============================================================
[01] every cached ESP32 project contributes to the cache key ... OK (0.056s)
     watched: u64ctrl (24 files), wifi/raw_c3 (17 files), wifi/raw_u64 (17 files)
[02] a misspelt directory fails instead of silently hashing nothing ... OK (0.040s)
     wifi/raw_c3/main misspelt as wifi/raw_c3/main_does_not_exist: esp_depends.py exited 1
     a change to any ESP32 project invalidates the cached binaries CI would otherwise hand back unbuilt
esp_depends_test: OK (2 checks, 0.097s)
esp-depends: OK (0.191s)

============================================================
E2E  input-batching  (overlay)
============================================================
[01] a batch of short taps is limited by the event count ... OK (0.003s)
     64 events, 3852 bytes
[02] a batch of long taps is limited by the body size ... OK (0.003s)
     46 events, 4014 bytes
[03] the file browser's 64-keypress field clear fits its requests ... OK (0.001s)
     64 taps as [60, 4]
[04] one oversized event is passed on rather than dropped ... OK (0.000s)
[05] no events make no requests ... OK (0.000s)
input_batching_test: OK (5 checks, 0.047s)
input-batching: OK (0.130s)

============================================================
E2E  runner-policy  (overlay)
============================================================
[01] a bare target is its own C64-side computer ... OK (0.000s)
[02] a cartridge target names the computer it is plugged into ... OK (0.000s)
[03] keyboard input is the computer's, everything else the device's ... OK (0.000s)
[04] a malformed target is rejected before anything starts ... OK (0.000s)
[05] a target's output path is safe to use as a file name ... OK (0.000s)
[06] a cartridge named alone takes the computer the bench declares ... OK (0.000s)
[07] an undeclared cartridge is unchanged, and so is a spelt-out one ... OK (0.000s)
[08] a declaration that says nothing about this device is ignored ... OK (0.000s)
[09] a declaration that names this device and no computer is fatal ... OK (0.000s)
[10] two spellings of the same machines are one target ...      WARN u2 names the same machines as u2@c64u; running it once
OK (0.000s)
[11] the capture reads every store except the clock ... OK (0.000s)
[12] a run that changed nothing writes nothing ... OK (0.000s)
[13] only the items a suite changed are written back ... OK (0.000s)
[14] a value the device will not take back is reported, not hidden ... OK (0.000s)
[15] the profiles are ordered and cumulative ... OK (0.000s)
[16] a suite named with -s runs whatever the profile says ... OK (0.000s)
[17] the manual suites arrive with the deep profile, not before ... OK (0.000s)
[18] the transports widen with depth, and -m still overrides ... OK (0.000s)
[19] an unknown profile is refused, naming the real ones ... OK (0.000s)
[20] move_rows spends page keys before single steps ... OK (0.000s)
[21] the keys sent always add up to the rows asked for ... OK (0.000s)
[22] a zero move sends nothing at all ... OK (0.000s)
[23] the matrix agrees with what the runner selects ... OK (0.000s)
[24] a suite run is counted once per transport swept ... OK (0.000s)
[25] the static view names no duration and no check count ... OK (0.000s)
[26] measured counts come from the run records ... OK (0.002s)
[27] two targets conflict exactly when they share a machine ... OK (0.000s)
[28] a target does not start while a machine it needs is in use ... OK (0.000s)
[29] scheduling every target eventually runs each one exactly once ... OK (0.000s)
[30] a child repeats the parent's work against one target ... OK (0.000s)
[31] every runner option is forwarded to a child or excluded on purpose ... OK (0.000s)
[32] children write into a directory of their own ... OK (0.000s)
[33] concurrent children never tear each other's lines ... OK (0.050s)
[34] every line a child printed reaches the run's output ... OK (0.042s)
[35] the run's status is the worst of its children's ... OK (0.000s)
[36] the gate reads the menu from the device and types into the computer ... OK (0.000s)
[37] a single-device target still sends everything to one host ... OK (0.000s)
[38] only a cartridge target has a computer menu to get out of the way ... OK (0.000s)
[39] a menu that will not open is repaired by resetting the machine ... OK (0.000s)
[40] a menu that will not close is repaired by resetting the machine ... OK (0.000s)
[41] a launcher is descended by reading the cursor, not by pressing blind ... OK (0.000s)
[42] a cartridge target shuts the computer's menu before resetting ... OK (1.005s)
[43] a UI that the reset does not free is reported, not looped on ... OK (0.000s)
[44] no suite builds a REST URL from an unrouted host ... OK (0.000s)
     8 routing forms accepted, tests/lib/rest.py is where the rule lives
[45] a clean run exits 0 ... OK (0.000s)
[46] a suite that needed a second attempt exits EXIT_RETRIED ... OK (0.000s)
[47] a failed suite exits EXIT_SUITE_FAILED ... OK (0.000s)
[48] a recovery with no failure exits EXIT_RECOVERED ... OK (0.000s)
[49] a recovery outranks a retry ... OK (0.000s)
[50] a failure outranks a recovery ... OK (0.000s)
[51] a device that cannot be made healthy outranks a failure ... OK (0.000s)
[52] the statuses are a scale, in severity order ... OK (0.000s)
[53] a device that answers is never recovered ... OK (0.000s)
[54] recovery runs only once the ordinary wait has given up ... OK (0.027s)
     fixture: the device is unhealthy: it is not answering
[55] a recovery that does not bring the device back reports so ... OK (0.076s)
     fixture: the device is unhealthy: it is not answering
[56] a recovery command that exits non-zero is still followed by a probe ... OK (0.013s)
     fixture: the device is unhealthy: it is not answering
[57] a recovery command that hangs is bounded by --recover-timeout ... OK (0.206s)
     fixture: the device is unhealthy: it is not answering
[58] a recovery command the shell cannot find is a failed recovery ... OK (0.082s)
     fixture: the device is unhealthy: it is not answering
[59] no recovery command means no recovery ... OK (0.000s)
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: no --recover-command was given
[60] a suite gives up after --recover-max-per-suite recoveries ... OK (0.140s)
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: this suite has already used its 2 recoveries
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: this suite has already used its 2 recoveries
[61] the per-suite budget starts again at the next suite ... OK (0.072s)
     fixture: the device is unhealthy: it is not answering
[62] the run gives up after --recover-max-total recoveries ... OK (0.153s)
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: the run has already used its 2 recoveries
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: the run has already used its 2 recoveries
[63] the refusal says which ceiling was reached ... OK (0.070s)
     fixture: the device is unhealthy: it is not answering
[64] a healthy sweep after a failed suite recovers nothing ... OK (0.001s)
     fixture: health: rest=9ms -> OK
[65] a degraded but reachable device is recovered ... OK (0.014s)
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: after recovery, health: rest=9ms -> OK
[66] a device still degraded after recovering is reported, not retried ... OK (0.016s)
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: after recovery, health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: still unhealthy after recovering: it is degraded: ftp
[67] a degraded device is not recovered past its ceiling ... OK (0.011s)
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: after recovery, health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: still unhealthy after recovering: it is degraded: ftp
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: not recovering: the run has already used its 1 recoveries
[68] --no-health-check takes the sweep out of the decision ... OK (0.000s)
[69] --no-health-check still recovers a device that has gone ... OK (0.069s)
     fixture: the device is unhealthy: it is not answering
[70] -o names the output directory ... OK (0.000s)
[71] --output-dir names the same one ... OK (0.000s)
[72] -j is not accepted ... OK (0.000s)
[73] --jsonl-dir is not accepted ... OK (0.000s)
[74] the old spellings are gone from --help ... OK (0.000s)
[75] the examples use the option the parser accepts ... OK (0.000s)
[76] a child is told where to write with the same option ... OK (0.000s)
[77] a run without --kernal starts the suite without one ... OK (0.000s)
[78] --kernal and --command-interface are passed to the suite ... OK (0.000s)
[79] a child run is given the same KERNAL options ... OK (0.000s)
[80] consecutive resets collapse into one ... OK (0.000s)
[81] a read between two resets does not warrant the second ... OK (0.000s)
[82] a mutation through another object still warrants a reset ... OK (0.000s)
[83] a GET through another object does not warrant a reset ... OK (0.000s)
[84] a mutating call between two resets warrants the second ... OK (0.000s)
[85] force resets even when nothing has changed ... OK (0.000s)
[86] a suite told the device was just reset does not reset again ... OK (0.000s)
[87] a suite not told that still resets ... OK (0.000s)
[88] the default runs a failing suite exactly three times ... OK (0.097s)
[89] --attempts counts executions, so 2 runs it twice ... OK (0.074s)
[90] --attempts 1 and --no-retry both run it exactly once ... OK (0.072s)
[91] a healthy device does not stop a failure being retried ... OK (0.104s)
[92] recoveries do not buy extra executions ... OK (0.090s)
[93] a device that has died by the last attempt is still noticed ... OK (0.090s)
[94] a device that cannot be made healthy ends the run ... OK (0.034s)
[95] a suite killed by a signal says so instead of reading as a failure ... OK (0.032s)
fixture: OK (1.500s)
[96] a health sweep reaches the JSONL with a latency per check ... OK (0.000s)
[97] a suite record carries the profile, attempt and recoveries ... OK (0.000s)
[98] the run record agrees with the exit status ... OK (0.000s)
[99] a sweep with every check passing is healthy ... OK (0.000s)
[100] a failed check makes the sweep degraded and names it ... OK (0.000s)
[101] a skipped check never makes the sweep degraded ... OK (0.000s)
[102] the one-line summary carries every latency and the verdict ... OK (0.000s)
     the recovery command runs on a degraded or unreachable device, never on a suite that merely failed
runner_policy_test: OK (102 checks, 2.765s)
runner-policy: OK (2.864s)

============================================================
E2E  stale-gates  (overlay)
============================================================
[01] a check that passes under an assumed fix is reported stale ... OK (0.002s)
[02] a tag in a per-suite file is found; run.jsonl alone would miss it ... OK (0.001s)
[03] a check that still fails under the assumption is not stale ... OK (0.000s)
[04] one failure among repeats keeps the entry off the stale list ... OK (0.000s)
[05] with no assumption, the gate skips for real and tags nothing ... OK (0.000s)
[06] assuming every fix does not tag a machine that never needed one ... OK (0.001s)
[07] a run with nothing tagged reports no stale gates at all ... OK (0.000s)
stale_gates_test: OK (7 checks, 0.008s)
stale-gates: OK (0.112s)

============================================================
E2E  openapi-validator  (overlay)
============================================================
[01] the core_version field decides which document applies ... OK (0.000s)
[02] a declared profile is used instead of asking the device ... OK (0.000s)
[03] a profile with no document is refused ... OK (0.000s)
[04] a request path finds the call it belongs to ... OK (0.000s)
     8 paths, including the three configs depths
[05] a path the document does not describe matches nothing ... OK (0.000s)
[06] an answer that matches is accepted ... OK (0.442s)
[07] a body missing a required member is refused ... OK (0.000s)
[08] a member of the wrong type is refused ... OK (0.000s)
[09] a status the call does not declare is refused ... OK (0.000s)
[10] the password refusal every call shares is accepted ... OK (0.000s)
[11] JSON where the document promises bytes is refused ... OK (0.000s)
[12] a body that will not parse is refused ... OK (0.000s)
[13] an undescribed call is refused unless the device says so too ... OK (0.000s)
[14] a verb the call does not have is refused ... OK (0.000s)
[15] anything outside /v1 is left alone ... OK (0.000s)
[16] both documents load and describe their own product ... OK (0.152s)
     u2 55 operations, u64 59 operations
openapi_contract_test: OK (16 checks, 0.786s)
openapi-validator: OK (0.901s)

============================================================
E2E  softiec-log  (overlay)
============================================================
[01] a command whose status read crossed a drive reset pairs with the line it wrote ... OK (0.000s)
[02] a reset outside a command's span leaves its status in force ... OK (0.000s)
[03] a drive reset sent during the status read makes the command optional ... OK (0.024s)
[04] the run's collected log gives this device's whole lines after the mark ... OK (0.005s)
[05] a line goes to the operation that needs it, not to an optional one before it ... OK (0.000s)
softiec_log_test: OK (5 checks, 0.108s)
softiec-log: OK (0.194s)

============================================================
E2E  navigation-keys  (overlay)
============================================================
[01] the two values, and an unknown one, resolve to a reading ... OK (0.000s)
[02] a letter is shifted only where the menu folds it back ... OK (0.000s)
[03] the menu cannot be made to read an uppercase letter under wasd ... OK (0.000s)
[04] the setting is read once per host ... OK (0.000s)
[05] a quick-seek prefix is spelt the way the machine will read it ... OK (0.000s)
[06] a name holding a cursor letter is still seeked for ... OK (0.000s)
[07] a popup's button key reaches the button rather than the cursor ... OK (0.000s)
[08] an overlay item is picked by the same spelling ... OK (0.000s)
[09] text typed into a field is never respelt ... OK (0.000s)
[10] a duration is read in every unit the suites use ... OK (0.000s)
[11] a duration that is not one is a usage error, not a traceback ... OK (0.000s)
[12] a duration has to be a length of time, so zero is refused ... OK (0.000s)
     a letter is shifted only where the firmware folds it back, so a field still receives what a suite typed
navigation_test: OK (12 checks, 0.042s)
navigation-keys: OK (0.127s)

============================================================
E2E  ui-backend-parse  (overlay)
============================================================
[01] a row marked by background colour is the selected one ... OK (0.000s)
[02] a blank row never wins, however it is coloured ... OK (0.000s)
[03] a screen with nothing marked says so, rather than guessing ... OK (0.000s)
[04] two entries are separated by the machine's own cursor colour ... OK (0.000s)
[05] a cursor that colours one field still marks its row ... OK (0.000s)
[06] a framed window is read instead of what it covers ... OK (0.000s)
[07] a window narrows what counts as a marking ... OK (0.000s)
[08] row_marks counts only the cells inside the window ... OK (0.000s)
[09] reverse video marks a row where no colour does ... OK (0.000s)
[10] over Telnet, a popup is the one frame inside the browser's ... OK (0.001s)
[11] over Telnet, a disk image's volume row is never taken for the cursor ... OK (0.001s)
[12] over Telnet, panels side by side are each a frame, and a column of one colour has no highlight ... OK (0.000s)
     the rules the parsers were written for, each as one screen
ui_backend_parse_test: OK (12 checks, 0.048s)
ui-backend-parse: OK (0.134s)

============================================================
E2E  keystroke-read-back  (overlay)
============================================================
[01] a field that shows the text is accepted after one typing ... OK (0.000s)
[02] a lost keystroke is typed again, and the retype is reported ... OK (0.153s)
[03] a field that never shows the text fails without being accepted ... OK (0.461s)
[04] the same name already in the listing does not stand in for the field ... OK (0.154s)
[05] a popup key that changes the screen is pressed once ... OK (0.000s)
[06] a popup key that changed nothing is pressed again, and the repeat is reported ... OK (0.153s)
[07] a popup key that never changes the screen fails ... OK (0.459s)
[08] an answer that opens a second popup with the same buttons is not repeated ... OK (0.000s)
[09] a lost quick-seek key is corrected before ENTER, so the named item runs ... OK (0.000s)
[10] a lost ENTER is pressed again, and runs the item once ... OK (0.153s)
[11] a highlight that cannot be read leaves the navigation as it was sent ... OK (0.000s)
[12] a transport that raises instead of reading the highlight still runs the item ... OK (0.000s)
[13] a context menu that opens is opened with one ENTER ... OK (0.000s)
[14] an ENTER the browser never saw is pressed again, and reported ... OK (0.156s)
[15] a screen that changed into something else gets no second ENTER ... OK (0.000s)
[16] a search term that arrives whole is typed once ... OK (0.000s)
[17] a doubled letter lost from a search term is typed again and reported ... OK (0.051s)
[18] a term the field never shows fails ... OK (0.155s)
     string boxes, popup keys, overlay items and search terms read back, against scripted key loss
keystroke_read_back_test: OK (18 checks, 1.950s)
keystroke-read-back: OK (2.036s)

============================================================
E2E  telnet-drain  (overlay)
============================================================
[01] a capture that sent nothing waits only the quiet check ... OK (0.158s)
[02] a key known to draw nothing costs no more than that either ... OK (0.155s)
[03] a redraw that never starts gives up after the first-byte budget ... OK (1.073s)
[04] one burst ends the drain an idle gap after it ... OK (0.205s)
[05] a settled command waits through the pause and takes both bursts ... OK (6.739s)
[06] an ordinary command does not wait for a second burst ... OK (0.652s)
[07] a redraw arriving in pieces is drained as one ... OK (0.314s)
[08] a band header refuses a layout it cannot label ... OK (0.001s)
telnet_drain_test: OK (8 checks, 9.348s)
telnet-drain: OK (9.472s)

============================================================
E2E  ui-backend-smoke  (overlay)
============================================================
     ui-backend-smoke: health: ping=22ms rest=9ms ftp=33ms telnet=28ms ident=10ms dma=7ms heap=5283524B raster=18ms jiffy=23ms -> OK

--- Machine identification
[01] each machine is recognised from its product string ... OK (0.000s)
[02] only the C64 Ultimate opens on a launcher ... OK (0.000s)
[03] the C64 Ultimate searches CommoServe, the others Assembly 64 ... OK (0.000s)
[04] an unrecognised product is refused rather than guessed ... OK (0.000s)
[05] the product is fetched once and then kept ... OK (0.000s)
[06] the firmware version is kept when the caller has it ... OK (0.000s)
[07] every fix in the table names a behaviour and a machine that lacks it ... OK (0.000s)
[08] the table decides which machine skips a tagged check ... OK (0.000s)
[09] a fix no entry lists is one every machine has ... OK (0.000s)
[10] a skipped check names the fix and the machine in its reason ... OK (0.000s)
[11] skip_without_fix answers True only where the check cannot run ... OK (0.000s)
[12] an assumed fix runs the checks it gates, which is how a backport is found ... OK (0.000s)
[13] an assumption list is parsed as a list, and a typo is refused ... OK (0.000s)
--- OK (13 checks, 0.000s)

--- Overlay navigation planner
[14] a unique first letter jumps straight to the item ... OK (0.000s)
[15] a shared first letter extends the prefix rather than walking ... OK (0.000s)
[16] walking wins when the item is already next to the cursor ... OK (0.000s)
[17] the seek lands on the first match and the walk covers the rest ... OK (0.000s)
[18] a prefix never contains a space, which selects rather than seeks ... OK (0.000s)
[19] a plan is never longer than walking from the top ... OK (0.000s)
[20] an upward walk is planned when that is the shorter route ... OK (0.000s)
--- OK (7 checks, 0.000s)

--- Cursor row from the colour plane
[21] a background-marked cursor row is found ... OK (0.000s)
[22] a cursor row marked by foreground colour alone is found ... OK (0.000s)
[23] the first entry is found when it is the selected one ... OK (0.000s)
[24] a cursor that colours only the name field is still found ... OK (0.000s)
[25] a volume header in reverse video is not taken for a cursor ... OK (0.000s)
[26] a form marks a field, not a row, and the field is found ... OK (0.000s)
[27] reverse video still marks the cursor when nothing else does ... OK (0.000s)
[28] a listing of three or more entries says which colour is the cursor ... OK (0.000s)
[29] a background-marked screen teaches no foreground cursor colour ... OK (0.000s)
[30] two entries need the colour the machine was measured to mark with ... OK (0.000s)
--- OK (10 checks, 0.001s)

--- Cursor row under a framed window
[31] a plain listing reports no framed window ... OK (0.000s)
[32] a framed window's own cursor is read, not the browser's ... OK (0.000s)
[33] a cursor further down a framed window is read ... OK (0.000s)
[34] the menu's header rule is not taken for a framed window ... OK (0.000s)
[35] a titled window's cursor is read, not its title ... OK (0.000s)
[36] a window's blank rows never win the cursor colour ... OK (0.000s)
[37] a dialog inside a picker is read, not the picker ... OK (0.000s)
[38] an open window is what the cursor colour is measured on ... OK (0.000s)
[39] a form teaches no cursor colour at all ... OK (0.000s)
[40] a menu beside a highlighted row is read, not the row ... OK (0.000s)
[41] a window with rounded corners is a window too ... OK (0.000s)
[42] an untitled window keeps its first row ... OK (0.000s)
[43] a background-marked window is read on a background-marked browser ... OK (0.000s)
--- OK (13 checks, 0.002s)

--- REST backend, Interface Type = Overlay on HDMI
[44] REST/Overlay: connect, navigate, teardown ... OK (2.473s)
--- OK (1 checks, 2.473s)

--- Telnet backend
[45] Telnet: connect, navigate, teardown ... OK (2.288s)
--- OK (1 checks, 2.288s)

--- REST backend, Interface Type = Freeze
[46] REST/Freeze: connect, navigate, teardown ... OK (2.136s)
--- OK (1 checks, 2.137s)
ui_backend_smoke_test: OK (46 checks, 6.960s)
ui-backend-smoke: OK (7.093s)

============================================================
E2E  readmem-writemem  (overlay)
============================================================
     readmem-writemem: health: ping=22ms rest=8ms ftp=33ms telnet=27ms ident=10ms dma=7ms heap=5273244B raster=20ms jiffy=51ms -> OK
[01] reset C64 to a clean, stable state ... OK (0.034s)
[02] read User Interface Settings config ... OK (0.012s)
     current Interface Type: Freeze

--- bounds: readmem rejects out-of-range parameters before allocating, and max-size reads do not degrade the device
[03] readmem rejects zero length ... OK (0.010s)
[04] readmem rejects negative length ... OK (0.011s)
[05] readmem rejects length one past the 64KB cap ... OK (0.009s)
[06] readmem rejects length far past the cap ... OK (0.010s)
[07] readmem rejects length that overflows a long ... OK (0.009s)
[08] readmem rejects read running past $FFFF ... OK (0.010s)
[09] readmem rejects address above $FFFF ... OK (0.010s)
[10] readmem rejects negative address ... OK (0.009s)
[11] 8 back-to-back max-size reads ($0000, 65536 bytes) all succeed ... OK (1.269s)
[12] 25 unsupported machine:measure calls leave readmem working ... OK (0.406s)
--- OK (10 checks, 1.774s)

--- bad-address: readmem and writemem reject an address that is not valid hex
[13] readmem rejects address='ZZZZ' ... OK (0.010s)
[14] writemem PUT rejects address='ZZZZ' ... OK (0.009s)
[15] writemem POST rejects address='ZZZZ' ... OK (0.036s)
[16] readmem rejects address='0xZZZZ' ... OK (0.010s)
[17] writemem PUT rejects address='0xZZZZ' ... OK (0.010s)
[18] writemem POST rejects address='0xZZZZ' ... OK (0.036s)
[19] readmem rejects address='gggg' ... OK (0.009s)
[20] writemem PUT rejects address='gggg' ... OK (0.010s)
[21] writemem POST rejects address='gggg' ... OK (0.043s)
[22] readmem rejects address='-0' ... OK (0.010s)
[23] writemem PUT rejects address='-0' ... OK (0.009s)
[24] writemem POST rejects address='-0' ... OK (0.057s)
[25] readmem rejects address='+1' ... OK (0.010s)
[26] writemem PUT rejects address='+1' ... OK (0.010s)
[27] writemem POST rejects address='+1' ... OK (0.059s)
[28] readmem rejects address='12GG' ... OK (0.009s)
[29] writemem PUT rejects address='12GG' ... OK (0.010s)
[30] writemem POST rejects address='12GG' ... OK (0.069s)
--- OK (18 checks, 0.427s)

--- bad-debugreg: machine:debugreg rejects a value that is not two hex digits
[31] debugreg rejects value='ZZ' ... OK (0.019s)
[32] debugreg rejects value='0xZZ' ... OK (0.019s)
[33] debugreg rejects value='-0' ... OK (0.019s)
[34] debugreg rejects value='1G' ... OK (0.018s)
[35] debugreg rejects value='1FF' ... OK (0.019s)
[36] debugreg still takes a valid value without changing it ... OK (0.010s)
[37] switch to Overlay on HDMI to probe live background activity ... OK (0.010s)
[38] probe addresses that change on their own while the C64 runs ... OK (5.636s)
     5 address(es) treated as expected live background noise: $00A1, $00A2, $00CD, $00CF, $01F2
[39] set Interface Type = Freeze ... OK (0.009s)
[40] open menu (Freeze) ... OK (0.092s)
[41] every 4 KB of RAM answers DMA while the machine is frozen ... OK (1.443s)
[42] probe addresses that still change while the machine is frozen ... OK (0.717s)
     0 address(es) change on their own while frozen
[43] write full-range pattern (Freeze) ... OK (0.759s)
[44] read back full range (Freeze) ... OK (0.161s)
[45] menu still open after write+read (Freeze) ... OK (0.012s)
--- OK (15 checks, 8.951s)

--- selfcheck-freeze: write and read both happen in Freeze (screen RAM excluded, the menu redraws its entry rows while open)
     [selfcheck-freeze] ignored ROM mismatches (writes to real ROM are no-ops): 16329
     [selfcheck-freeze] screen ($0400-$07FF) mismatches: 907 (expected, menu owns this range while frozen)
     [selfcheck-freeze] ignored known-live-noise mismatches: 0
     [selfcheck-freeze] color RAM (low nibble) mismatches: 0
     [selfcheck-freeze] RAM mismatches: 0
[46] close menu (Freeze) ... OK (0.094s)
[47] set Interface Type = Overlay on HDMI ... OK (0.010s)
[48] open menu (Overlay on HDMI) ... OK (0.091s)
[49] pause C64 for exact round trip (Overlay on HDMI) ... OK (0.008s)
[50] write full-range pattern (Overlay on HDMI) ... OK (0.570s)
[51] read back full range (Overlay on HDMI) ... OK (0.156s)
[52] resume C64 after exact round trip (Overlay on HDMI) ... OK (0.009s)
[53] menu still open after write+read (Overlay on HDMI) ... OK (0.013s)
--- OK (8 checks, 0.978s)

--- selfcheck-overlay-on-hdmi: write and read both happen in Overlay on HDMI (screen RAM excluded, the menu redraws its entry rows while open)
     [selfcheck-overlay-on-hdmi] ignored ROM mismatches (writes to real ROM are no-ops): 0
     [selfcheck-overlay-on-hdmi] screen ($0400-$07FF) mismatches: 908 (expected, menu owns this range while frozen)
     [selfcheck-overlay-on-hdmi] ignored known-live-noise mismatches: 0
     [selfcheck-overlay-on-hdmi] color RAM (low nibble) mismatches: 0
     [selfcheck-overlay-on-hdmi] RAM mismatches: 0
[54] close menu (Overlay on HDMI) ... OK (0.096s)
[55] close menu for the screen round trip (Overlay on HDMI) ... OK (0.010s)
[56] pause C64 for the screen round trip (Overlay on HDMI) ... OK (0.010s)
[57] write and read back $0400-$07FF (Overlay on HDMI) ... OK (0.064s)
[58] resume C64 after the screen round trip (Overlay on HDMI) ... OK (0.009s)
--- OK (5 checks, 0.217s)

--- screen-round-trip-overlay-on-hdmi: screen RAM round-trips exactly with no menu open
     [screen-round-trip-overlay-on-hdmi] screen ($0400-$07FF) mismatches: 0 (expected 0)
[59] set Interface Type = Overlay on HDMI ... OK (0.011s)
[60] open menu (Overlay on HDMI) ... OK (0.090s)
[61] write full-range pattern (Overlay on HDMI) ... OK (0.485s)
[62] capture ground truth in Overlay on HDMI mode ... OK (0.152s)
[63] close menu (Overlay on HDMI) ... OK (0.095s)
[64] set Interface Type = Freeze ... OK (0.010s)
[65] open menu (Freeze) ... OK (0.090s)
[66] read full range (Freeze) ... OK (0.154s)
[67] menu still open after cross-mode read (Freeze) ... OK (0.012s)
--- OK (9 checks, 1.105s)

--- overlay-on-hdmi-write_freeze-read: bytes written while Overlay on HDMI must read back the same while Freeze, except screen RAM (menu-owned while frozen)
     [overlay-on-hdmi-write_freeze-read] screen ($0400-$07FF) mismatches: 113 (expected, menu owns this range while frozen)
     [overlay-on-hdmi-write_freeze-read] ignored known-live-noise mismatches: 0
     [overlay-on-hdmi-write_freeze-read] color RAM (low nibble) mismatches: 0
     [overlay-on-hdmi-write_freeze-read] RAM mismatches: 0
[68] close menu (Freeze) ... OK (0.092s)
[69] set Interface Type = Freeze ... OK (0.010s)
[70] open menu (Freeze) ... OK (0.092s)
[71] write full-range pattern (Freeze) ... OK (0.559s)
[72] capture ground truth in Freeze mode ... OK (0.153s)
[73] close menu (Freeze) ... OK (0.093s)
[74] set Interface Type = Overlay on HDMI ... OK (0.010s)
[75] open menu (Overlay on HDMI) ... OK (0.093s)
[76] read full range (Overlay on HDMI) ... OK (0.154s)
[77] menu still open after cross-mode read (Overlay on HDMI) ... OK (0.012s)
--- OK (10 checks, 1.296s)

--- freeze-write_overlay-on-hdmi-read: bytes written while Freeze must read back the same while Overlay on HDMI, except screen RAM (menu-owned while frozen)
     [freeze-write_overlay-on-hdmi-read] screen ($0400-$07FF) mismatches: 113 (expected, menu owns this range while frozen)
     [freeze-write_overlay-on-hdmi-read] ignored known-live-noise mismatches: 0
     [freeze-write_overlay-on-hdmi-read] color RAM (low nibble) mismatches: 0
     [freeze-write_overlay-on-hdmi-read] RAM mismatches: 0
[78] close menu (Overlay on HDMI) ... OK (0.092s)
     restored Interface Type to 'Freeze'
--- OK (1 checks, 0.319s)

--- summary
     bounds: OK
     bad-address: OK
     bad-debugreg: OK
     selfcheck-freeze: OK
     selfcheck-overlay: OK
     screen-round-trip: OK
     overlay-to-freeze: OK
     freeze-to-overlay: OK
readmem_writemem_test: OK (78 checks, 21.2s)
readmem-writemem: OK (21.3s)

============================================================
E2E  menu-screen  (overlay)
============================================================
     menu-screen: health: ping=15ms rest=8ms ftp=33ms telnet=29ms ident=10ms dma=6ms heap=5272676B raster=20ms jiffy=20ms -> OK
[01] open menu ... OK (0.010s)
[02] GET menu_screen contract ... OK (0.011s)
[03] menu_screen renders a real menu ... OK (0.000s)
[04] close menu ... OK (0.009s)
[05] GET menu_screen unavailable ... OK (0.009s)
[06] GET menu_screen auth ... SKIP (--password not supplied, 0.000s)
menu_screen_test: OK (6 checks, 0.790s)
menu-screen: OK (0.915s)

============================================================
E2E  input  (overlay)
============================================================
     input: health: ping=9ms rest=9ms ftp=31ms telnet=28ms ident=10ms dma=6ms heap=5269200B raster=20ms jiffy=21ms -> OK
[01] input snapshot has stable empty response shape ... OK (0.026s)
[02] POST accepts 64 event batch ... OK (0.054s)
[03] bad content-type is rejected without mutation ... OK (0.052s)
[04] missing JSON body is rejected without mutation ... OK (0.050s)
[05] malformed JSON is rejected without mutation ... OK (0.052s)
[06] unknown root field is rejected without mutation ... OK (0.053s)
[07] late invalid event keeps whole batch atomic ... OK (0.056s)
[08] joystick port 1 fire keeps Anykey buttons 2 and 3 released ... OK (0.299s)
[09] joystick port 1 fire2 lights only Anykey button 2, only on port 1 ... OK (0.297s)
[10] joystick port 1 fire3 lights only Anykey button 3, only on port 1 ... OK (0.303s)
[11] joystick port 2 fire keeps Anykey buttons 2 and 3 released ... OK (0.301s)
[12] joystick port 2 fire2 lights only Anykey button 2, only on port 2 ... OK (0.300s)
[13] joystick port 2 fire3 lights only Anykey button 3, only on port 2 ... OK (0.300s)
[14] joystick fire2 on one port and fire3 on the other stay independent ... OK (0.300s)
[15] joystick fire2/fire3 tap auto-releases the POT hardware state ... OK (0.390s)
[16] joystick port 1 up press is visible on CIA reads ... OK (0.091s)
[17] joystick port 1 all inputs and idempotent release are visible on CIA reads ... OK (0.092s)
[18] joystick port 2 diagonal and fire are visible on CIA reads ... OK (0.089s)
[19] joystick partial release is visible on CIA reads ... OK (0.093s)
[20] joystick fire2/fire3 round-trip through REST state and POT hardware ... OK (0.710s)
[21] joystick fire2/fire3 held on port 2 leave port 1's Anykey buttons released ... OK (0.589s)
[22] joystick release in the same batch as a tap on the same input wins ... OK (0.030s)
[23] joystick release_all then press in same batch is visible on CIA reads ... OK (0.078s)
[24] joystick unusual combination is visible on CIA reads ... OK (0.078s)
[25] joystick tap does not release persistent input ... OK (0.292s)
[26] joystick tap auto releases ... OK (0.290s)
[27] invalid joystick batch does not mutate state ... OK (0.118s)
[28] joystick release_all clears both ports, including POT hardware ... OK (0.348s)
[29] machine reset clears keyboard and joystick REST state ... OK (3.099s)
[30] keyboard single-tap batch is consumed by BASIC in order ... OK (0.677s)
[31] keyboard 10 Hz mixed alphabet echo has no missed presses ... OK (2.860s)
[32] keyboard 20 Hz alternating ab echo has no missed presses ... OK (1.481s)
[33] keyboard 5 Hz alternating ab echo has no missed presses ... OK (0.801s)
[34] keyboard single letter reaches the live C64 matrix ... OK (0.116s)
[35] keyboard shifted pair reaches the live C64 matrix ... OK (0.175s)
[36] keyboard batch applies multiple presses atomically ... OK (0.175s)
[37] keyboard ordered batch and idempotent release ... OK (0.059s)
[38] keyboard release_all can be followed by press in same batch ... OK (0.056s)
[39] keyboard accepts eight simultaneous inputs ... OK (0.069s)
[40] keyboard tap does not release persistent key ... OK (0.159s)
[41] keyboard release_all clears state ... OK (0.055s)
[42] keyboard restore tap auto releases ... OK (0.240s)
[43] keyboard special-key taps snapshot correctly and auto release ... OK (2.414s)
[44] keyboard tap is visible in the live hardware snapshot and auto releases ... OK (0.252s)
[45] keyboard cursor-left tap is visible in the live hardware snapshot and auto releases ... OK (0.255s)
[46] keyboard tap batch drains through the live matrix path ... OK (1.385s)
[47] keyboard long repeated tap train drains fully without sticky state ... OK (4.237s)
[48] invalid keyboard batch does not mutate state ... OK (0.038s)
[49] mouse state is detached and empty after release_all ... OK (0.027s)
[50] a mouse button press attaches the mouse and is reported in order ... OK (0.093s)
[51] invalid mouse events are rejected with their error and change nothing ... OK (0.522s)
[52] a tap presses and releases, and a path is queued behind it ... OK (0.471s)
[53] the queue holds 1024 reports and refuses a batch beyond that with 429 ... OK (0.465s)
[54] a request over the JSON value limit is refused with a clear error ... OK (0.027s)
[55] release_all in a batch makes room for what follows it ... OK (5.256s)
[56] release_all detaches the mouse ... OK (0.041s)
[57] a machine reset detaches the mouse and drops its queue ... OK (3.121s)
input_test: OK (57 checks, 41.5s)
input: OK (41.8s)

@barryw

barryw commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Default E2E gate, ./run-tests -H 192.168.1.148, against the rebased branch (30afb19 on master 2c60bd8; Ultimate 64 Elite, FPGA 125, core 1.50). Part 2 of 2.

Default gate log, part 2 of 2
============================================================
E2E  create-disk-image  (overlay)
============================================================
     create-disk-image: health: ping=23ms rest=8ms ftp=33ms telnet=29ms ident=10ms dma=6ms heap=5269900B raster=20ms jiffy=49ms -> OK

--- preconditions
[01] device reachable ... OK (0.012s)
[02] clear leftovers from an earlier run ... OK (0.170s)
--- OK (2 checks, 0.183s)

--- d64-35
[03] create d64 at /Temp/cdi_d64-35.d64 ... OK (0.050s)
[04] device still answers after create d64 ... OK (0.010s)
[05] /Temp/cdi_d64-35.d64 is 174848 bytes ... OK (0.012s)
--- OK (3 checks, 0.073s)

--- d64-40
[06] create d64 at /Temp/cdi_d64-40.d64 ... OK (0.056s)
[07] device still answers after create d64 ... OK (0.008s)
[08] /Temp/cdi_d64-40.d64 is 196608 bytes ... OK (0.013s)
--- OK (3 checks, 0.077s)

--- d71
[09] create d71 at /Temp/cdi_d71.d71 ... OK (0.096s)
[10] device still answers after create d71 ... OK (0.010s)
[11] /Temp/cdi_d71.d71 is 349696 bytes ... OK (0.012s)
--- OK (3 checks, 0.117s)

--- d81
[12] create d81 at /Temp/cdi_d81.d81 ... OK (0.154s)
[13] device still answers after create d81 ... OK (0.009s)
[14] /Temp/cdi_d81.d81 is 819200 bytes ... OK (0.013s)
--- OK (3 checks, 0.176s)

--- dnp-1
[15] create dnp at /Temp/cdi_dnp-1.dnp ... OK (0.035s)
[16] device still answers after create dnp ... OK (0.009s)
[17] /Temp/cdi_dnp-1.dnp is 65536 bytes ... OK (0.012s)
--- OK (3 checks, 0.057s)

--- dnp-16
[18] create dnp at /Temp/cdi_dnp-16.dnp ... OK (0.155s)
[19] device still answers after create dnp ... OK (0.018s)
[20] /Temp/cdi_dnp-16.dnp is 1048576 bytes ... OK (0.021s)
--- OK (3 checks, 0.194s)

--- repeat
[21] create d64 at /Temp/cdi_d64-35-repeat.d64 ... OK (0.069s)
[22] device still answers after create d64 ... OK (0.009s)
[23] /Temp/cdi_d64-35-repeat.d64 is 174848 bytes ... OK (0.015s)
--- OK (3 checks, 0.194s)
create_disk_image_test: OK (23 checks, 1.120s)
create-disk-image: OK (1.226s)

============================================================
E2E  d81-track-bounds  (overlay)
============================================================
     d81-track-bounds: health: ping=22ms rest=8ms ftp=33ms telnet=28ms ident=11ms dma=6ms heap=5268360B raster=20ms jiffy=20ms -> OK
[01] prepare 1581 and generated D81 fixtures ... OK (13.6s SLOW)

--- missing-track-81
[02] mount 80-track D81 on drive b ... OK (0.141s)
[03] device remains reachable after mount ... OK (0.012s)
[04] physical track 81 has the expected result ... OK (4.965s)
     tracks=80, job=$00, io=$00
--- OK (3 checks, 5.119s)

--- track-81
[05] mount 81-track D81 on drive b ... OK (0.137s)
[06] device remains reachable after mount ... OK (0.010s)
[07] physical track 81 has the expected result ... OK (4.960s)
     tracks=81, job=$00, io=$00
--- OK (3 checks, 5.107s)

--- oversized
[08] mount 85-track D81 on drive b ... OK (0.140s)
[09] device remains reachable after mount ... OK (0.010s)
[10] physical track 81 has the expected result ... OK (4.895s)
     tracks=85, job=$00, io=$00
--- OK (3 checks, 5.422s)
d81_track_bounds_test: OK (10 checks, 29.3s)
d81-track-bounds: OK (29.4s)

============================================================
E2E  rest-api-coverage  (overlay)
============================================================
     rest-api-coverage: health: ping=106ms rest=21ms ftp=99ms telnet=32ms ident=12ms dma=7ms heap=5276472B raster=22ms jiffy=19ms -> OK

--- coverage
[01] read the operation list ... OK (59 operations, from 2 OpenAPI document(s), 0.917s)
[02] every operation is exercised or excluded ... OK (58 exercised, 1 excluded, 0.000s)
[03] nothing is classified that the device does not serve ... OK (0.000s)
[04] every operation has a happy path somewhere ... OK (41 here, 9 elsewhere, 8 with none, 0.000s)
     not called: PUT /v1/machine:poweroff - turns the device off, and no test can turn it back on
     negative only: POST /v1/drives/{drive}:load_rom - as the PUT form
     negative only: POST /v1/runners:modplay - as the PUT form
     negative only: POST /v1/runners:run_crt - as the PUT form
     negative only: POST /v1/runners:sidplay - as the PUT form
     negative only: PUT /v1/drives/{drive}:load_rom - a wrong file leaves the drive with a broken ROM until the next reboot, and the tree has no drive ROM to load a good one from
     negative only: PUT /v1/runners:modplay - needs a MOD file and takes over the machine
     negative only: PUT /v1/runners:run_crt - needs a cartridge image and resets the C64
     negative only: PUT /v1/runners:sidplay - needs a SID file and takes over the machine
--- OK (4 checks, 0.917s)

--- preconditions
[05] device reachable ... OK (0.041s)
[06] record the state to restore ... OK (drive a: enabled=True type='1541', 9 settings, 0.154s)
--- OK (2 checks, 0.195s)

--- operations
     79 cases x3, concurrent, 3 workers
[07] GET /v1/version - returns a version x3 ... OK (0.001s)
[08] GET /v1/info - names product and firmware x3 ... OK (0.000s)
[09] GET /v1/info - reports the interface MAC addresses x3 ... OK (0.000s)
[10] GET /v1/info - names the commit it was built from x3 ... OK (0.000s)
[11] GET /v1/help - answers for a known command x3 ... OK (0.000s)
[12] GET /v1/help - refuses a missing command x3 ... OK (0.000s)
[13] GET /v1/configs - lists categories x3 ... OK (0.000s)
[14] GET /v1/configs/{category} - reads one category x3 ... OK (0.000s)
[15] GET /v1/configs/{category}/{item} - reads one item x3 ... OK (0.000s)
[16] GET /v1/configs/{category} - refuses an unknown category x3 ... OK (0.000s)
[17] PUT /v1/configs/{category}/{item} - writing the current value back is a no-op x3 ... OK (0.000s)
[18] PUT /v1/configs/{category}/{item}/{value} - the value-in-path form sets the same item x3 ... OK (0.000s)
[19] PUT /v1/configs/{category}/{item} - refuses an unknown category x3 ... OK (0.000s)
[20] POST /v1/configs - the JSON form sets an item x3 ... OK (0.000s)
[21] POST /v1/configs - refuses an unknown category x3 ... OK (0.000s)
[22] PUT /v1/configs:save_to_flash - save then load leaves the value x3 ... OK (0.000s)
[23] PUT /v1/configs:load_from_flash - covered by the save case above x3 ... OK (0.000s)
[24] PUT /v1/configs/{category}:save_to_flash - save then load leaves the value x3 ... OK (0.000s)
[25] PUT /v1/configs/{category}:load_from_flash - covered by the save case above x3 ... OK (0.000s)
[26] PUT /v1/configs/{category}:reset_to_default - resets one category and the values restore x3 ... OK (0.000s)
[27] PUT /v1/configs:reset_to_default - resets everything, needs --allow-global-reset x3 ... SKIP (resets every category at once; pass --allow-global-reset, 0.000s)
[28] GET /v1/files/{path}:info - describes /Temp x3 ... OK (0.000s)
[29] GET /v1/files/{path}:info - reports a missing path as absent x3 ... OK (0.000s)
[30] PUT /v1/files/{path}:create_d64 - refuses an unwritable path x3 ... OK (0.000s)
[31] PUT /v1/files/{path}:create_d71 - refuses an unwritable path x3 ... OK (0.000s)
[32] PUT /v1/files/{path}:create_d81 - refuses an unwritable path x3 ... OK (0.000s)
[33] PUT /v1/files/{path}:create_dnp - refuses an unwritable path x3 ... OK (0.000s)
[34] GET /v1/machine:readmem - returns the length asked for x3 ... OK (0.000s)
[35] GET /v1/machine:readmem - refuses a zero length x3 ... OK (0.000s)
[36] GET /v1/machine:heap - reports free within total x3 ... OK (0.000s)
[37] GET /v1/machine:measure - answers, 501 without bus measurement x3 ... OK (0.000s)
[38] GET /v1/machine:input - reports the input state x3 ... OK (0.000s)
[39] GET /v1/machine:debugreg - reads the register x3 ... OK (0.000s)
[40] PUT /v1/machine:debugreg - writing the held value back is a no-op x3 ... OK (0.000s)
[41] GET /v1/machine:menu_screen - answers 404 with no menu open x3 ... OK (0.000s)
[42] PUT /v1/machine:writemem - the byte written is the byte read back x3 ... OK (0.000s)
[43] POST /v1/machine:writemem - the uploaded block reads back x3 ... OK (0.000s)
[44] PUT /v1/machine:writemem - refuses a missing data argument x3 ... OK (0.000s)
[45] POST /v1/machine:input - refuses an empty event list x3 ... OK (0.000s)
[46] PUT /v1/machine:pause - pauses and resumes (status only) x3 ... OK (0.000s)
[47] PUT /v1/machine:resume - covered by the pause case above x3 ... OK (0.000s)
[48] PUT /v1/machine:menu_button - opens the menu and closes it again x3 ... OK (0.000s)
[49] PUT /v1/machine:reset - the C64 comes back to READY x3 ... OK (0.000s)
[50] PUT /v1/machine:reboot - the C64 comes back to READY x3 ... OK (0.000s)
[51] GET /v1/drives - lists both drive slots x3 ... OK (0.000s)
[52] PUT /v1/drives/{drive}:on - flips the drive and the listing agrees x3 ... OK (0.000s)
[53] PUT /v1/drives/{drive}:off - covered by the on/off case above x3 ... OK (0.000s)
[54] PUT /v1/drives/{drive}:mount - the listing shows the image x3 ... OK (0.000s)
[55] PUT /v1/drives/{drive}:unlink - keeps the disk, drops the file x3 ... OK (0.000s)
[56] PUT /v1/drives/{drive}:remove - clears the mounted image x3 ... OK (0.000s)
[57] POST /v1/drives/{drive}:mount - an uploaded image mounts x3 ... OK (0.000s)
[58] PUT /v1/drives/{drive}:set_mode - setting the current mode is a no-op x3 ... OK (0.000s)
[59] PUT /v1/drives/{drive}:reset - resets the drive (status only) x3 ... OK (0.000s)
[60] PUT /v1/drives/{drive}:reset - refuses an unknown drive letter x3 ... OK (0.000s)
[61] PUT /v1/drives/{drive}:mount - refuses a missing image x3 ... OK (0.000s)
[62] PUT /v1/drives/{drive}:load_rom - refuses a missing ROM file x3 ... OK (0.000s)
[63] POST /v1/drives/{drive}:load_rom - refuses a request with no body x3 ... OK (0.000s)
[64] PUT /v1/drives/{drive}:set_mode - refuses an unknown mode x3 ... OK (0.000s)
[65] PUT /v1/drives/{drive}:remove - refuses an unknown drive letter x3 ... OK (0.000s)
[66] PUT /v1/drives/{drive}:on - refuses an unknown drive letter x3 ... OK (0.000s)
[67] PUT /v1/drives/{drive}:off - refuses an unknown drive letter x3 ... OK (0.000s)
[68] PUT /v1/drives/{drive}:unlink - refuses an unknown drive letter x3 ... OK (0.000s)
[69] PUT /v1/runners:sidplay - refuses a missing file x3 ... OK (0.000s)
[70] POST /v1/runners:sidplay - refuses a request with no body x3 ... OK (0.000s)
[71] PUT /v1/runners:modplay - refuses a missing file x3 ... OK (0.000s)
[72] POST /v1/runners:modplay - refuses a request with no body x3 ... OK (0.000s)
[73] PUT /v1/runners:load_prg - refuses a missing file x3 ... OK (0.000s)
[74] POST /v1/runners:load_prg - refuses a request with no body x3 ... OK (0.000s)
[75] PUT /v1/runners:run_prg - refuses a missing file x3 ... OK (0.000s)
[76] POST /v1/runners:run_prg - refuses a request with no body x3 ... OK (0.000s)
[77] PUT /v1/runners:run_crt - refuses a missing file x3 ... OK (0.000s)
[78] POST /v1/runners:run_crt - refuses a request with no body x3 ... OK (0.000s)
[79] PUT /v1/streams/{stream}:start - starts and stops the debug stream x3 ... OK (0.000s)
[80] PUT /v1/streams/{stream}:stop - covered by the start case above x3 ... OK (0.000s)
[81] PUT /v1/streams/{stream}:start - refuses an unknown stream x3 ... OK (0.000s)
[82] PUT /v1/streams/{stream}:stop - refuses an unknown stream x3 ... OK (0.000s)
[83] protocol: an unknown route is 404 x3 ... OK (0.000s)
[84] protocol: a command asked for with the wrong method does not run x3 ... OK (0.000s)
[85] protocol: an unauthenticated read is refused x3 ... SKIP (no password is set on this device, 0.000s)
--- SKIP (79 checks, 23.6s)
rest_api_coverage_test: OK (85 checks, 24.8s)
rest-api-coverage: OK (25.0s)

============================================================
E2E  openapi-contract  (overlay)
============================================================
     openapi-contract: health: ping=18ms rest=10ms ftp=34ms telnet=28ms ident=10ms dma=6ms heap=5271156B raster=20ms jiffy=51ms -> OK
[01] u2 document is valid OpenAPI 3.1 ... OK (0.696s)
     44 paths, 33 schemas
[02] u64 document is valid OpenAPI 3.1 ... OK (0.743s)
     47 paths, 35 schemas
     Ultimate 64 Elite is described by the u64 document
[03] the device serves its own OpenAPI document ... OK (0.378s)
     148565 bytes, byte for byte the committed u64 document
[04] the device serves the API explorer ... OK (0.055s)
[05] a client generates from the u64 document ... OK (1.127s)
     package ultimate_64_rest_api_client
[06] the generated client reads the interface version ... OK (0.102s)
     version 0.1
[07] the generated client reads memory and agrees with the raw call ... OK (0.021s)
     $D020 reads fef6
[08] GET /v1/version matches the document ... OK (0.009s)
[09] GET /v1/info matches the document ... OK (0.011s)
[10] GET /v1/machine:heap matches the document ... OK (0.010s)
[11] GET /v1/drives matches the document ... OK (0.016s)
[12] GET /v1/configs matches the document ... OK (0.012s)
[13] GET /v1/configs/Drive%20A%20Settings matches the document ... OK (0.014s)
[14] GET /v1/configs/Drive%20A%20Settings/* matches the document ... OK (0.060s)
[15] GET /v1/machine:readmem matches the document ... OK (0.009s)
[16] GET /v1/machine:menu_screen matches the document ... OK (0.010s)
     HTTP 404
[17] a rejected call matches the document ... OK (0.010s)
[18] a refused call matches the document ... SKIP (no network password is configured on this device, 0.000s)
openapi_contract_test: OK (18 checks, 4.722s)
openapi-contract: OK (4.925s)

============================================================
E2E  browser-long-filename  (overlay)
============================================================
     browser-long-filename: health: ping=15ms rest=9ms ftp=42ms telnet=30ms ident=10ms dma=7ms heap=5268004B raster=20ms jiffy=51ms -> OK
[01] reset the machine to a clean starting state ... OK (0.653s)
[02] seed long-name fixture for rename ... OK (0.541s)
[03] browser rename on long filename ... OK (3.939s)
[04] reseed long-name fixture for mount ... OK (0.656s)
[05] browser mount on long filename ... OK (3.875s)
browser_long_filename_test: OK (5 checks, 10.4s)
browser-long-filename: OK (11.4s)

============================================================
E2E  ftp-client  (overlay)
============================================================
     ftp-client: health: ping=34ms rest=11ms ftp=33ms telnet=29ms ident=16ms dma=21ms heap=5285036B raster=20ms jiffy=20ms -> OK
     inferred --ftp-advertised-host 192.168.1.78
     controlled FTP server on 0.0.0.0:2121 (advertised 192.168.1.78)
[I 2026-10-01 15:00:30] concurrency model: async
[I 2026-10-01 15:00:30] masquerade (NAT) address: 192.168.1.78
[I 2026-10-01 15:00:30] passive ports: 30000->30019
[I 2026-10-01 15:00:30] >>> starting FTP server on 0.0.0.0:2121, pid=42555 <<<
     server root: /var/folders/52/5656d2050tz7nvc4b1m1tfqw0000gn/T/u64ftp-4_9sfwdn (marker E2E-1790881230)
[01] GET /v1/version reachable ... OK (0.1, 0.017s)
[02] menu closed -> menu_screen 404 ... OK (0.032s)
[03] menu_button opens menu ... OK (0.097s)
[04] menu_screen returns 2000-byte matrix ... OK (2000 bytes, 0.013s)
[05] input release_all works ... OK (0.013s)
[06] menu closes from anywhere ... OK (0.308s)
[07] remove any stale hosts left by a prior run ... OK (0 removed, 1.460s)

--- stage: smoke
[08] create FTP host 'E2EFTP12314flb' through the New Host form ... OK (7.583s)
[09] new alias appears under /ftp ... OK (1.665s)
[10] enter host: server sees login + TYPE I + LIST ... [I 2026-10-01 15:00:44] 192.168.1.148:52432-[] FTP session opened (connect)
[I 2026-10-01 15:00:44] 192.168.1.148:52432-[u64e2e] USER 'u64e2e' logged in.
OK (PASS,TYPE,LIST, 2.604s)
[11] root listing shows fixture entries ... OK (README,DIRA,SMALL, 0.013s)
[12] open README.TXT -> RETR (52 bytes) ... [I 2026-10-01 15:00:46] 192.168.1.148:52432-[u64e2e] RETR /private/var/folders/52/5656d2050tz7nvc4b1m1tfqw0000gn/T/u64ftp-4_9sfwdn/README.TXT completed=1 bytes=52 seconds=0.001
OK (52 bytes, 2.366s)
[13] remove host and confirm it disappears ... [I 2026-10-01 15:00:50] 192.168.1.148:52432-[u64e2e] FTP session closed (disconnect).
OK (5.511s)
--- OK (6 checks, 19.7s)

--- cleanup
     preserved: nothing

--- summary
     Phase    Operation              Result             Commands       Fixture/Notes
     ------------------------------------------------------------------------------
     smoke    create host            OK                                E2EFTP12314flb
     smoke    list host              OK                                E2EFTP12314flb
     smoke    enter/LIST             OK                 USER/PASS/TYPE E2EFTP12314flb
     smoke    root entries           OK                 LIST           README/DIRA/SMALL
     smoke    RETR README            OK                 RETR           README.TXT 52 bytes
     smoke    remove host            OK                                E2EFTP12314flb
     ------------------------------------------------------------------------------
     Total REST requests   : 125
     Total menu snapshots  : 56
     Total keyboard events : 87
     Total FTP commands    : 24
     Created host alias    : (removed)
     Remote root path      : /var/folders/52/5656d2050tz7nvc4b1m1tfqw0000gn/T/u64ftp-4_9sfwdn
     Cleanup status        : clean
Exception in thread Thread-1 (serve_forever):
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 1041, in _bootstrap_inner
    self.run()
    ~~~~~~~~^^
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 992, in run
    self._target(*self._args, **self._kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/barry/Library/Python/3.13/lib/python/site-packages/pyftpdlib/servers.py", line 254, in serve_forever
    self.ioloop.loop(timeout, blocking)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "/Users/barry/Library/Python/3.13/lib/python/site-packages/pyftpdlib/ioloop.py", line 376, in loop
    poll(timeout)
    ~~~~^^^^^^^^^
  File "/Users/barry/Library/Python/3.13/lib/python/site-packages/pyftpdlib/ioloop.py", line 741, in poll
    kevents = self._kqueue.control(
        None, _len(self.socket_map), timeout
    )
OSError: [Errno 9] Bad file descriptor
ftp_client_test: OK (6 operation(s), 23.6s)
ftp-client: OK (23.9s)

============================================================
E2E  prg-load-path-trim  (overlay)
============================================================
     prg-load-path-trim: health: ping=25ms rest=8ms ftp=34ms telnet=28ms ident=10ms dma=6ms heap=5272580B raster=19ms jiffy=49ms -> OK

--- Ultimate 64 PRG load path trim
     host: 192.168.1.148
     PUT file path: /Temp/rest-prg-path-trim-target-example.prg
     POST upload name: rest-prg-path-trim-upload-example.prg

--- 1. Capture Current Configuration
     Temp Auto Cleanup: Enabled
     Temp Subfolders: Enabled

--- 2. Configure Temp Settings
[01] set Temp Auto Cleanup to Enabled ... OK (0.010s)
[02] set Temp Subfolders to Enabled ... OK (0.011s)
--- OK (2 checks, 0.021s)

--- 3. Prepare PRG Fixture
[03] upload PUT fixture rest-prg-path-trim-target-example.prg ... OK (0.082s)
--- OK (1 checks, 0.083s)

--- 4. Validate PUT Endpoints
[04] exercise PUT runners:load_prg ... OK (0.396s)
     PUT load_prg boot-cart name: ...T-EXAMPLE
[05] exercise PUT runners:run_prg ... OK (1.773s)
     PUT run_prg boot-cart name: ...T-EXAMPLE
--- OK (2 checks, 2.277s)

--- 5. Validate POST Endpoints
[06] exercise POST runners:load_prg ... OK (0.446s)
     POST load_prg boot-cart name: ...D-EXAMPLE
[07] exercise POST runners:run_prg ... OK (1.795s)
     POST run_prg boot-cart name: ...D-EXAMPLE
--- OK (2 checks, 2.751s)

--- 6. Validate a P00 wrapper through the same runner
[08] upload P00 fixture rest-prg-wrap.p00 ... OK (0.092s)
[09] exercise PUT runners:load_prg on a P00 wrapper ... OK (0.393s)
     P00 boot-cart name: WRAPPED PROGRAM
[10] the program behind the header is what was loaded ... OK (0.009s)
--- OK (3 checks, 0.550s)
prg_load_path_trim_test: OK (10 checks, 6.334s)

--- restore User Interface Settings
[11] set Temp Auto Cleanup to Enabled ... OK (0.009s)
[12] set Temp Subfolders to Enabled ... OK (0.010s)
prg-load-path-trim: OK (7.196s)

============================================================
E2E  cfg-single-group  (overlay)
============================================================
     cfg-single-group: health: ping=19ms rest=7ms ftp=37ms telnet=28ms ident=10ms dma=6ms heap=5270920B raster=20ms jiffy=53ms -> OK
[01] load a one-group CFG file through the browser ... OK (5.170s)
cfg_single_group_test: OK (1 checks, 5.879s)
cfg-single-group: OK (6.420s)

============================================================
E2E  cfg-loader-log  (overlay)
============================================================
[01] an uninterrupted log gives the stores it names ... OK (0.000s)
[02] a log with no loader lines in it reports nothing ... OK (0.000s)
[03] a spliced effectuated record yields no store name ... OK (0.000s)
[04] both halves of a spliced effectuated record are reported ... OK (0.000s)
[05] a record appended to another task's line is reported, not read ... OK (0.000s)
[06] a spliced clean record yields no store name and is reported ... OK (0.000s)
cfg_loader_log_test: OK (6 checks, 0.066s)
cfg-loader-log: OK (0.147s)

============================================================
E2E  printer  (overlay)
============================================================
     printer: health: ping=16ms rest=9ms ftp=42ms telnet=29ms ident=10ms dma=7ms heap=5267860B raster=21ms jiffy=54ms -> OK
[01] REST reachable at 192.168.1.148 ... OK (0.014s)
[02] reset before run ... OK (0.076s)
[03] load printer_e2e.prg ... OK (914 bytes, 0.000s)
[04] capture original Printer Settings ... OK (0.133s)
[05] print epson/bitmap: apply settings + run PRG ... OK (phase=printer closed heartbeat=4, 2.652s)
[06] print epson/bitmap: Flush/Eject via on-screen menu ... OK (4.138s)
     the flush held the menu for 2.4s

--- restoring original Printer Settings
     IEC printer: Disabled
     Bus ID: 4
     Output file: /Usb0/printer
     Output type: PNG B&W
     Ink density: Medium
     Page top margin (default is 5): 5
     Page height (default is 60): 60
     Emulation: Commodore MPS
     Commodore charset: USA/UK
     Epson charset: Basic
     IBM table 2: International 1

--- summary
     epson      bitmap PASS_NO_VERIFY       /USB1/printer/e2e-eb18b7
printer_test: OK (1 combination(s), 8.439s)
printer: OK (8.574s)

============================================================
E2E  usb-mouse  (overlay)
============================================================
     usb-mouse: health: ping=28ms rest=9ms ftp=33ms telnet=28ms ident=10ms dma=6ms heap=5264852B raster=26ms jiffy=49ms -> OK
     saved settings: Mouse Mode='Mouse', Mouse Sensitivity=8, Mouse Acceleration='Off', Mouse Wheel Sensitivity=8, Mouse Wheel Direction='Normal', Joystick Swapper='Normal', Menu Mouse Navigation='Enabled', System Mode='NTSC'
     quick profile: move, precision, buttons, wheel-micromys, cursor-mode
[01] moves [(30, 20)] land at (30, 20) ... OK (0.842s)
     x=30 y=20 step x(0, 30) y(0, 20) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$54/$70 lines=$00 frames=28 moved=1
[02] moves [(-80, -65)] land at (-80, -65) ... OK (0.632s)
     x=-80 y=-65 step x(-31, 0) y(-31, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$78/$1A lines=$00 frames=32 moved=3
[03] moves [(63, -63), (-20, 40)] land at (43, -23) ... OK (0.641s)
     x=43 y=-23 step x(-20, 31) y(-31, 31) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$6E/$46 lines=$00 frames=32 moved=4
[04] 120 reports of 1,-1 move a frame each ... OK (3.292s)
     x=120 y=-120 step x(0, 1) y(-1, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$08/$08 lines=$00 frames=191 moved=120
[05] 120 reports of -2,2 move a frame each ... OK (3.278s)
     x=-240 y=240 step x(-2, 0) y(0, 2) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$38/$38 lines=$00 frames=191 moved=120
[06] all 8 button states ... OK (1.484s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=4 middle=2 right=1] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=84 moved=0
[07] a REST tap of each button, and of all three, is one press each ... OK (0.950s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=2 middle=2 right=2] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=55 moved=0
[08] sensitivity 3: 2 up and 1 down detents, slowly, give 3 pulses each and leave the position ... OK (1.924s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=6 down=3 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=112 moved=0
[09] 10 detents as fast as the host polls queue and all pulse ... OK (1.356s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=10 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=79 moved=0
[10] Cursor, sensitivity 2: wheel -2/0 types cursor keys {'up': 0, 'down': 8, 'left': 0, 'right': 0} ... OK (1.097s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[down=8] keys held=0 [0.7=8] pot=$18/$18 lines=$00 frames=65 moved=0
[11] Cursor, sensitivity 2: wheel 0/1 types cursor keys {'up': 0, 'down': 0, 'left': 3, 'right': 0} ... OK (0.790s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[left=3] keys held=0 [0.2=3 6.4=3] pot=$18/$18 lines=$00 frames=47 moved=0
[12] Cursor: [(8, 0)] types {'right': 2} and leaves the pointer ... OK (0.565s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[right=2] keys held=0 [0.2=2] pot=$18/$18 lines=$00 frames=33 moved=0
[13] Cursor: [(-12, 0)] types {'left': 3} and leaves the pointer ... OK (0.625s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[left=3] keys held=0 [0.2=3 6.4=3] pot=$18/$18 lines=$00 frames=36 moved=0
[14] Cursor: [(0, 8)] types {'down': 2} and leaves the pointer ... OK (0.555s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[down=2] keys held=0 [0.7=2] pot=$18/$18 lines=$00 frames=33 moved=0
[15] Cursor: [(0, -2)] types nothing and leaves the pointer ... OK (0.482s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=28 moved=0
[16] Cursor: [(0, -2), (0, -2)] types {'up': 1} and leaves the pointer ... OK (0.541s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[up=1] keys held=0 [0.7=1 6.4=1] pot=$18/$18 lines=$00 frames=32 moved=0
[17] Cursor: [(1, 0), (1, 0), (1, 0)] types nothing and leaves the pointer ... OK (0.593s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=34 moved=0
[18] Cursor: [(1, 0), (1, 0), (1, 0), (1, 0)] types {'right': 1} and leaves the pointer ... OK (0.659s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[right=1] keys held=0 [0.2=1] pot=$18/$18 lines=$00 frames=39 moved=0
[19] Cursor: motion far faster than the keys can be typed stops soon after the mouse ... OK (1.625s)
     keys kept coming for 1.03s after the mouse stopped: x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[right=19] keys held=0 [0.2=19] pot=$18/$18 lines=$00 frames=96 moved=0
[20] Cursor: turning the other way drops the keys still waiting ... OK (0.745s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[left=1 right=4] keys held=0 [0.2=5 6.4=1] pot=$18/$18 lines=$00 frames=44 moved=0
mouse_test: OK (20 checks, 25.4s)
usb-mouse: OK (25.5s)

============================================================
E2E  ftp-server  (overlay)
============================================================
     ftp-server: health: ping=16ms rest=10ms ftp=33ms telnet=29ms ident=10ms dma=6ms heap=5263084B raster=20ms jiffy=20ms -> OK

--- a name the listing reports can address the file it names
[01] a 40-character name is listed unchanged ... OK (0.043s)
[02] that file is removed by the name the listing reported ... OK (0.048s)
[03] a 100-character name is listed unchanged ... OK (0.049s)
     100 characters survived the listing
[04] SIZE answers for the long name the listing reported ... OK (0.030s)
[05] that file is removed by the name the listing reported ... OK (0.050s)
--- OK (5 checks, 0.283s)
ftp_server_test: OK (5 checks, 0.290s)
ftp-server: OK (0.378s)

============================================================
E2E  assembly64  (overlay)
============================================================
     assembly64: health: ping=17ms rest=8ms ftp=33ms telnet=29ms ident=10ms dma=6ms heap=5256576B raster=20ms jiffy=20ms -> OK
     this machine searches Assembly 64, from its task menu

--- the form opens from the menu and closes again
[01] open the Assembly 64 Query Form ... OK (1.519s)
[02] the form leaves cleanly with RUN/STOP ... OK (0.286s)
--- OK (2 checks, 1.876s)

--- a real query is sent to Assembly 64
[03] open the form and enter the first field ... OK (0.837s)
     the query term goes in 'Name:', the form's first field
[04] the typed term lands in the first field ... OK (0.633s)
[05] the service answers and the results match what was asked for ... OK (0.968s)
     20 result rows mention 'turrican'
--- OK (3 checks, 2.802s)

--- dropdown selections preserve the query form (#863)
[06] the form draws fields, and at least one of them is a dropdown ... OK (7.869s)
     13 field(s) drawn: Name:, Group:, Handle:, Event:, Repo:, Category:, Subcat:, Rating:, Type:, Date:, Latest:, Sort:, Order:
     4 free text: Name:, Group:, Handle:, Event:
     9 dropdown: Repo:, Category:, Subcat:, Rating:, Type:, Date:, Latest:, Sort:, Order:
[07] populate the free-text fields ... OK (3.718s)
[08] Repo: +/- preserves existing fields ... OK (0.805s)
[09] cancelling Repo: preserves existing fields ... OK (0.497s)
[10] confirming Repo: preserves its value and all other fields ... OK (2.015s)
[11] Category: +/- preserves existing fields ... OK (1.029s)
[12] cancelling Category: preserves existing fields ... OK (0.445s)
[13] confirming Category: preserves its value and all other fields ... OK (2.022s)
[14] Subcat: +/- preserves existing fields ... OK (1.034s)
[15] cancelling Subcat: preserves existing fields ... OK (0.492s)
[16] confirming Subcat: preserves its value and all other fields ... OK (1.971s)
[17] Rating: +/- preserves existing fields ... OK (1.066s)
[18] cancelling Rating: preserves existing fields ... OK (0.493s)
[19] confirming Rating: preserves its value and all other fields ... OK (2.037s)
[20] Type: +/- preserves existing fields ... OK (1.054s)
[21] cancelling Type: preserves existing fields ... OK (0.516s)
[22] confirming Type: preserves its value and all other fields ... OK (2.039s)
[23] Date: +/- preserves existing fields ... OK (1.051s)
[24] cancelling Date: preserves existing fields ... OK (0.508s)
[25] confirming Date: preserves its value and all other fields ... OK (2.052s)
[26] Latest: +/- preserves existing fields ... OK (1.051s)
[27] cancelling Latest: preserves existing fields ... OK (0.527s)
[28] confirming Latest: preserves its value and all other fields ... OK (2.037s)
[29] Sort: +/- preserves existing fields ... OK (1.039s)
[30] cancelling Sort: preserves existing fields ... OK (0.490s)
[31] confirming Sort: preserves its value and all other fields ... OK (2.014s)
[32] Order: +/- preserves existing fields ... OK (1.281s)
[33] cancelling Order: preserves existing fields ... OK (0.497s)
[34] confirming Order: preserves its value and all other fields ... OK (2.010s)
--- OK (29 checks, 44.0s)

--- the menu button must work from inside the edit field
[35] open the form and enter the edit field ... OK (0.738s)
[36] the menu button closes the menu from inside the field ... OK (0.084s)
[37] the menu opens again, so the UI task left string_edit ... OK (0.084s)
--- OK (3 checks, 1.263s)

--- an aborted edit must not wedge the form
[38] open the form, type into a field, then abort ... OK (1.259s)
[39] the form is still usable after the abort ... OK (0.012s)
--- OK (2 checks, 1.630s)

--- over-long and empty input
[40] type more than the field accepts ... OK (1.952s)
[41] an empty query is refused rather than sent ... OK (1.723s)
[42] the warning is dismissed and the form is still usable ... OK (0.262s)
--- OK (3 checks, 4.306s)

--- a user mashing keys while the form is busy
[43] submit a query and hammer keys while it runs ... OK (4.168s)
[44] the device is still answering after the mashing ... OK (0.006s)
--- OK (2 checks, 4.538s)

--- opening and abandoning the form repeatedly
[45] open and abandon the form three times ... OK (2.462s)
[46] the menu button closes and reopens the form three times ... OK (3.009s)
--- OK (2 checks, 5.696s)
assembly64_test: OK (46 checks, 66.6s)
assembly64: OK (66.7s)

============================================================
E2E  freezer-audio  (overlay)
============================================================
     freezer-audio: health: ping=22ms rest=8ms ftp=33ms telnet=29ms ident=12ms dma=5ms heap=5227120B raster=20ms jiffy=18ms -> OK
[01] entering the freezer menu is silent on a silent machine ... OK (0.625s)
     entry peak RMS 0.001
[02] a SID tune reaches the audio stream ... OK (2.026s)
     baseline RMS 0.107
[03] entering the freezer menu is silent while a tune plays ... OK (0.122s)
     entry peak RMS 0.096 against a 0.107 tune
[04] the freezer menu silences the machine ... OK (0.402s)
     frozen RMS 0.000, under the 0.011 bound
[05] closing the freezer menu gives the sound back ... OK (1.245s)
     resumed RMS 0.107
[06] playing a SID from the freezer menu gives the sound back ... OK (2.154s)
     relaunched RMS 0.107
freezer_audio_test: OK (6 checks, 7.522s)
freezer-audio: OK (7.649s)

TEARDOWN (overlay): release input ... OK (0.013s)
TEARDOWN (overlay): close active menu UI ... OK (0.024s)
TEARDOWN (overlay): reset machine ... OK (0.040s)

settings: restore 192.168.1.148 ... OK (unchanged, 0.237s)

============================================================
Summary
============================================================
     OK   e2e overlay    310s (31 ok)
     slowest: assembly64 66.7s, input 41.8s, d81-track-bounds 29.4s
     310s in suites, 19.5s in health checks, the UI-state gate and teardown
     330s total

OK  all 31 suite runs passed.

@barryw

barryw commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

uci-targets (standard profile, carries the palette and palette-stream scenarios), same build:

uci-targets log

============================================================
Ultimate hardware test run
============================================================
     target:     192.168.1.148
     categories: e2e
     profile:    quick (default)
     modes:      overlay
     timeout:    30.0s
     on failure: continue
     health:    answers + health sweep, up to 3 attempts per suite
settings: capture 192.168.1.148 ... OK (197 settings in 18 stores, 0.258s)
machine: resets skip the RAM test ... OK (already set, 0.013s)
the machine keeps its own bus resources ... OK (already set, 0.012s)

============================================================
E2E  uci-targets  (overlay)
============================================================
     uci-targets: health: ping=52ms rest=9ms ftp=37ms telnet=31ms ident=15ms dma=4ms heap=5244112B raster=19ms jiffy=22ms -> OK
[01] reset the C64 so the command interface starts idle ... OK (3.046s)
[02] read 'C64 and Cartridge Settings' ... OK (0.021s)
     current: {'Command Interface': 'Enabled', 'RAM Expansion Unit': 'Disabled', 'REU Preload Image': '/Usb0/preload.reu', 'REU Size': '2 MB', 'REU Preload Offset': '0 KB'}
[03] enable the Command Interface registers at $DF1B-$DF1F ... OK (0.055s)
[04] the command interface is idle once enabled ... OK (0.023s)
[05] transport: an empty command returns an empty reply ... OK (0.103s)
     <empty> -> 0.02s, data b'', status b''
[06] transport: unregistered target answers IDENTIFY ... OK (0.552s)
     0f 01 -> 0.05s, data b'NO TARGET', status b'00,OK'
[07] transport: unregistered target rejects anything else ... OK (0.683s)
     0f 7f -> 0.06s, data b'', status b'21,UNKNOWN COMMAND'
[08] transport: a no-reply command returns straight to Idle ... OK (0.128s)
[09] transport: pushing while a reply is pending sets and clears ERROR ... OK (0.899s)
[10] transport: the next command is not prefixed by leftover bytes ... OK (0.813s)
     04 28 00 -> 0.07s, data b'Ultimate 64 Elite', status b'00,OK'
[11] transport: ABORT releases a pending reply back to Idle ... OK (0.144s)
[12] control-target: IDENTIFY answers with the target name ... OK (0.854s)
     04 01 -> 0.06s, data b'CONTROL TARGET V1.1', status b'00,OK'
[13] control-target: an unimplemented command is reported as unknown ... OK (0.685s)
     04 7f -> 0.06s, data b'', status b'21,UNKNOWN COMMAND'
[14] control-target: LOAD_REU without a filename is rejected, not run ... OK (0.656s)
     04 08 -> 0.06s, data b'', status b'81,INVALID PARAMS'
[15] control-target: SAVE_REU without a filename is rejected, not run ... OK (0.656s)
     04 09 -> 0.06s, data b'', status b'81,INVALID PARAMS'
[16] control-target: GET_HWINFO rejects a device number it does not have ... OK (0.671s)
     04 28 02 -> 0.07s, data b'', status b'81,INVALID PARAMS'
     04 28 00 -> 0.07s, data b'Ultimate 64 Elite', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[17] palette: GET_PALETTE returns 16 RGB colors ... OK (1.872s)
     04 51 -> 0.10s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[18] palette: SET_PALETTE_COLOR changes only the requested color ... OK (2.053s)
     04 53 0f e8 e8 e8 -> 0.11s, data b'', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xe8\xe8\xe8', status b'00,OK'
[19] palette: color index 16 is rejected ... OK (0.713s)
     04 53 10 00 00 00 -> 0.11s, data b'', status b'81,INVALID PARAMS'
[20] palette: SET_PALETTE replaces all 16 RGB colors ... OK (2.681s)
     04 52 0b 30 55 7a 9f c4 e9 0e 33 58 7d a2 c7 ec 11 36 5b 80 a5 ca ef 14 39 5e 83 a8 cd f2 17 3c 61 86 ab d0 f5 1a 3f 64 89 ae d3 f8 1d 42 67 8c b1 d6 -> 0.74s, data b'', status b'00,OK'
     04 51 -> 0.06s, data b'\x0b0Uz\x9f\xc4\xe9\x0e3X}\xa2\xc7\xec\x116[\x80\xa5\xca\xef\x149^\x83\xa8\xcd\xf2\x17<a\x86\xab\xd0\xf5\x1a?d\x89\xae\xd3\xf8\x1dBg\x8c\xb1\xd6', status b'00,OK'
[21] palette: short SET_PALETTE payload is rejected ... OK (1.327s)
     04 52 00 00 00 f7 f7 f7 8d 2f 34 6a d4 cd 98 35 a4 4c b4 42 2c 29 b1 ef ef 5d 98 4e 20 5b 38 00 d1 67 6d 4a 4a 4a 7b 7b 7b 9f ef 93 6d 6a ef b2 b2 -> 0.73s, data b'', status b'81,INVALID PARAMS'
[22] palette: GET_PALETTE rejects a payload ... OK (0.670s)
     04 51 00 -> 0.07s, data b'', status b'81,INVALID PARAMS'
[23] palette: RESET_PALETTE restores the built-in C64 colors ... OK (1.996s)
     04 54 -> 0.06s, data b'', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[24] palette: RESET_PALETTE rejects a payload ... OK (0.667s)
     04 54 00 -> 0.07s, data b'', status b'81,INVALID PARAMS'
[25] palette: restore the original runtime palette ... OK (2.681s)
     04 52 00 00 00 f7 f7 f7 8d 2f 34 6a d4 cd 98 35 a4 4c b4 42 2c 29 b1 ef ef 5d 98 4e 20 5b 38 00 d1 67 6d 4a 4a 4a 7b 7b 7b 9f ef 93 6d 6a ef b2 b2 b2 -> 0.74s, data b'', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[26] issue-740-matrix: confirm /Temp/uci_e2e_absent.reu does not exist ... OK (0.011s)
[27] issue-740-matrix: put a 16384-byte image at /Temp/uci_e2e_present.reu ... OK (0.091s)
[28] issue-740-matrix: REU Enabled, image absent, name at offset 2 ... OK (3.164s)
     04 08 52 45 55 2e 49 4d 47 -> 0.16s, data b"\xff\xff\xff\xffREU LOAD: FAILED TO OPEN /TEMP/UCI_E2E_ABSENT.REU: FILE DOESN'T EXIST", status b'85,REU FILE CANNOT BE OPENED'
[29] issue-740-matrix: REU Enabled, image absent, name at offset 4 ... OK (3.231s)
     04 08 00 00 52 45 55 2e 49 4d 47 -> 0.19s, data b"\xff\xff\xff\xffREU LOAD: FAILED TO OPEN /TEMP/UCI_E2E_ABSENT.REU: FILE DOESN'T EXIST", status b'85,REU FILE CANNOT BE OPENED'
[30] issue-740-matrix: REU Disabled, image absent, name at offset 2 ... OK (0.947s)
     04 08 52 45 55 2e 49 4d 47 -> 0.16s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[31] issue-740-matrix: REU Disabled, image absent, name at offset 4 ... OK (0.978s)
     04 08 00 00 52 45 55 2e 49 4d 47 -> 0.19s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[32] issue-740-matrix: REU Enabled, image present, name at offset 2 ... OK (2.496s)
     04 08 52 45 55 2e 49 4d 47 -> 0.12s, data b'\x00@\x00\x00REU LOAD: LOADED 16384 BYTES TO $000000 FROM /TEMP/UCI_E2E_PRESENT.REU', status b'00,OK'
[33] issue-740-matrix: REU Enabled, image present, name at offset 4 ... OK (2.605s)
     04 08 00 00 52 45 55 2e 49 4d 47 -> 0.19s, data b'\x00@\x00\x00REU LOAD: LOADED 16384 BYTES TO $000000 FROM /TEMP/UCI_E2E_PRESENT.REU', status b'00,OK'
[34] save-reu-offset-past-end: put the preload offset at the end of a 128 KB REU ... OK (0.073s)
[35] save-reu-offset-past-end: SAVE_REU reports the offset and saves nothing ... OK (2.752s)
     04 09 52 45 55 2e 49 4d 47 -> 0.16s, data b'\xfd\xff\xff\xffREU SAVE: OFFSET 131072 >= REU SIZE 131072, SKIPPING', status b'86,REU OFFSET > SIZE. NOT SAVED'
[36] load-reu-disabled: disable the REU ... OK (0.025s)
[37] load-reu-disabled: leave text in the reply buffer from a previous command ... OK (0.812s)
     04 28 00 -> 0.07s, data b'Ultimate 64 Elite', status b'00,OK'
[38] load-reu-disabled: command reports the REU is disabled and replies with 4 bytes ... OK (0.898s)
     04 08 52 45 55 2e 49 4d 47 -> 0.15s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[39] save-reu-disabled: disable the REU ... OK (0.025s)
[40] save-reu-disabled: leave text in the reply buffer from a previous command ... OK (0.812s)
     04 28 00 -> 0.07s, data b'Ultimate 64 Elite', status b'00,OK'
[41] save-reu-disabled: command reports the REU is disabled and replies with 4 bytes ... OK (0.898s)
     04 09 52 45 55 2e 49 4d 47 -> 0.16s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[42] softiec-single-part-reply: SoftIEC target answers IDENTIFY ... OK (1.000s)
     05 01 -> 0.06s, data b'SOFTWARE IEC TARGET V1.0', status b'00,OK'
[43] softiec-single-part-reply: LOAD_SU on a missing file completes in one part ... OK (0.435s)
     05 10 00 00 00 00 00 00 4e 4f 53 55 43 48 46 49 4c 45 -> 0.29s, data b'', status b'\x01'
[44] softiec-single-part-reply: GET_FATNAME reply completes in one part ... OK (0.427s)
     05 22 02 23 -> 0.09s, data b'/buffer', status b'\x00'
[45] softiec-single-part-reply: an unimplemented SoftIEC command is reported as unknown ... OK (0.207s)
     05 7f -> 0.06s, data b'', status b'\x04'
[46] softiec-x00-name: put a P00 file, and a file named like one, on the RAM disk ... OK (0.079s)
[47] softiec-x00-name: a full path to a P00 file answers the name in its header and PRG ... OK (0.602s)
     05 23 2f 54 65 6d 70 2f 55 43 49 47 41 4d 45 2e 50 30 30 -> 0.25s, data b'\x01MY GAME', status b'\x00'
[48] softiec-x00-name: the same file without its directory answers the host name ... OK (0.697s)
     05 23 55 43 49 47 41 4d 45 2e 50 30 30 -> 0.21s, data b'\x00UCIGAME.P00', status b'\x00'
[49] softiec-x00-name: a file without the signature answers its host name ... OK (0.828s)
     05 23 2f 54 65 6d 70 2f 55 43 49 50 4c 41 49 4e 2e 50 30 30 -> 0.31s, data b'\x00UCIPLAIN.P00', status b'\x00'
[50] softiec-setting-modes: IEC Drive Enabled: on the bus True, UCI True, $DF1B 11 ... OK (1.021s)
     05 01 -> 0.06s, data b'SOFTWARE IEC TARGET V1.0', status b'00,OK'
     on the bus True, $DF1B 11, IDENTIFY status b'00,OK'
[51] softiec-setting-modes: IEC Drive Disabled: on the bus False, UCI False, $DF1B 31 ... OK (0.231s)
     05 01 -> 0.06s, data b'', status b'\x05'
     on the bus False, $DF1B 31, IDENTIFY status b'\x05'
[52] softiec-setting-modes: IEC Drive UCI Only: on the bus False, UCI True, $DF1B 11 ... OK (1.030s)
     05 01 -> 0.06s, data b'SOFTWARE IEC TARGET V1.0', status b'00,OK'
     on the bus False, $DF1B 11, IDENTIFY status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[53] palette-stream: a refused palette value leaves the debug stream running ... OK (0.671s)
[54] palette-stream: palette=0 sends no palette packets ... OK (1.501s)
[55] palette-stream: a palette change on an ordinary stream sends nothing ... OK (1.792s)
     04 53 0f e8 e8 e8 -> 0.08s, data b'', status b'00,OK'
[56] palette-stream: an opted-in start sends the current palette at once ... OK (0.021s)
[57] palette-stream: the palette repeats once a second ... OK (2.000s)
[58] palette-stream: a palette change is sent at once ... OK (1.295s)
     04 53 0f 17 17 17 -> 0.08s, data b'', status b'00,OK'
[59] palette-stream: rapid changes are coalesced without stopping the video ... OK (6.789s)
     04 51 -> 0.04s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
     04 51 -> 0.04s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B#h\x8b\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
     240 changes in 1.03 s -> 41 palette packets at 39.8/s; 3695 video packets, 0 lost on the way here, longest gap 11 ms
[60] palette-stream: RESET_PALETTE reaches an opted-in client at once ... OK (2.061s)
     04 54 -> 0.06s, data b'', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[61] palette-stream: a later ordinary start is not opted in ... OK (1.553s)
[62] palette-stream: restore the original runtime palette ... OK (2.603s)
     04 52 00 00 00 f7 f7 f7 8d 2f 34 6a d4 cd 98 35 a4 4c b4 42 2c 29 b1 ef ef 5d 98 4e 20 5b 38 00 d1 67 6d 4a 4a 4a 7b 7b 7b 9f ef 93 6d 6a ef b2 b2 b2 -> 0.68s, data b'', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[63] interface-usable-after: the control target still answers IDENTIFY ... OK (0.850s)
     04 01 -> 0.06s, data b'CONTROL TARGET V1.1', status b'00,OK'
     restored C64 and Cartridge Settings: {'Command Interface': 'Enabled', 'RAM Expansion Unit': 'Disabled', 'REU Preload Image': '/Usb0/preload.reu', 'REU Size': '2 MB', 'REU Preload Offset': '0 KB'}

--- summary
     transport: OK
     control-target: OK
     palette: OK
     issue-740-matrix: OK
     save-reu-offset-past-end: OK
     load-reu-disabled: OK
     save-reu-disabled: OK
     softiec-single-part-reply: OK
     softiec-x00-name: OK
     softiec-setting-modes: OK
     palette-stream: OK
     interface-usable-after: OK
     cleanup: OK
uci_targets_test: OK (63 checks, 79.8s)
uci-targets: OK (79.9s)

TEARDOWN (overlay): release input ... OK (0.017s)
TEARDOWN (overlay): close active menu UI ... OK (0.024s)
TEARDOWN (overlay): reset machine ... OK (0.045s)

settings: restore 192.168.1.148 ... OK (unchanged, 0.232s)

============================================================
Summary
============================================================
     OK   e2e overlay    79.9s (1 ok)
     79.9s in suites, 2.463s in health checks, the UI-state gate and teardown
     82.4s total

OK  all 1 suite runs passed.

@barryw

barryw commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Added 72a4373, which tightens the tests. The missing-feature and multicast checks were run red first; the reuse check could not be made to fail on its own (see below).

  • A missing feature now fails instead of skipping. palette-stream used the refused palette value as its capability probe, so firmware that lost the feature reported a skip and the run stayed green. The machines that lack it are now declared in the fix table (vic-palette-stream, only the C64 Ultimate's release firmware). Red on master + Ignore input held while the menu opens #940 (no palette streams): FAIL ... "Function start does not have parameter palette". Green on this branch.
  • The 224–231 multicast change is covered. A stream to 230.0.1.64 has to arrive as multicast. Measured by hand on master + Ignore input held while the menu opens #940: video:start returned 200, but no packets reached the host in 1.5 s, while 239.0.1.64 delivered 5,379 in the same window. On this branch: 3,580 packets in 1.0 s, at TTL 64, so not routed. The check itself passes on this branch.
  • Palette socket reuse. Twenty opted-in starts and stops each have to deliver a palette packet. lwIP's UDP PCB pool is 8 (MEMP_NUM_UDP_PCB, lwipopts.h:197), shared with DHCP, DNS and the other UDP users. Green on this branch. I also tried a build that drops the socket without closing it. It fails the scenario, but earlier, at the first opted-in start, because the socket binds the fixed source port 53248. So this check guards the stop/start path explicitly, and the existing first-start check catches a socket that isn't closed.

After the change, uci-targets passes on this branch: palette 9/9, palette-stream 13/13.

Not covered: the Ultimate 64-II. data_streamer.cc ships there too, and I only have a U64 Elite. uci-targets is a standard-profile suite, so a bare ./run-tests (quick) doesn't run these. @chrisgleissner, your call whether palette-stream should run in quick, by moving the suite or splitting the scenario out.

@chrisgleissner chrisgleissner added network Networking and remote services: REST, Socket DMA, FTP, Telnet, and online access. video VIC emulation, rendering, video timing, HDMI, and analogue video output. labels Oct 3, 2026
@chrisgleissner

Copy link
Copy Markdown
Collaborator

Hi @barryw , thanks for fixing the merge conflict in this PR.

barryw added 13 commits October 4, 2026 17:46
Emit the active 16-color RGB palette through the software UDP stack on VIC stream start and runtime updates. Reserve line 0x7fff so the FPGA stream remains unchanged.

Refs GideonZ#850
Protect legacy video receivers from short palette datagrams while allowing opted-in clients to follow runtime VIC colors.

Refs GideonZ#850
Pair each generation with one atomic RGB snapshot and cap burst updates at one packet per 20 ms. Exercise 240 runtime changes while checking coalescing, packet contents, and uninterrupted video sequencing.
A conditional indefinite wait can miss a rapid stop/start transition and leave runtime palette delivery asleep. Retain the proven one-second poll while preserving notification coalescing.
Binding the palette socket to the FPGA wired address drops unicast packets when a dual-homed Ultimate routes software UDP through Wi-Fi. Keep multicast bound to the FPGA source and let LwIP select the unicast interface.
Firmware:
- Resolve a stream start into a copy and commit the source, destination,
  enable and palette opt-in together, so a failed start leaves the running
  stream and its palette packets untouched.
- Carry the opt-in as a mode bit instead of the command's filename, which the
  task menu fills with the name of the highlighted browser entry.
- Validate the palette parameter before the debug stream is stopped, so a
  rejected request changes nothing.
- Bind the palette socket to the VIC stream's interface and its source port
  53248, so palette and video arrive from one address and port for unicast
  and multicast alike. Connect it to the destination, drain it, and close it
  when the stream stops, so datagrams sent to it cannot hold lwIP's netbufs.
- Keep the socket across transient send failures and pace failed attempts.
- Program the palette registers inside the same critical section as the
  shadow copy and generation, so the VIC, GET_PALETTE and the stream agree.
- Treat all of 224.0.0.0/4 as multicast, not only 232-239.
- Space palette packets at least 20 ms apart and leave the palette task
  asleep when no stream has opted in.
- Document that the opt-in is device-wide and the latest start sets it.

Tests:
- Move the stream checks into their own palette-stream scenario after the
  command interface scenarios, skipping when the firmware, the link or the -H
  address cannot carry the stream.
- Drain the stream on a thread, judge the video by stalls rather than host
  drops, and use video as the positive control for the no-palette checks.
- Require prompt palette packets on start, change and reset, and check the
  repeat interval.
- Start the burst fixture only on GO=1 and reset before restoring the palette
  when a failure leaves it parked.
- Share one palette parser in streams.py and pass stream start parameters
  through Arming.

Refs GideonZ#850
…low multicast

The palette-stream scenario used the rejected palette value as its
capability probe, so firmware that lost the feature reported a skip and
the run stayed green. The machines that lack it are now declared in the
fix table (vic-palette-stream: only the C64 Ultimate's release firmware),
and everywhere else a refused palette parameter fails the scenario.

Two checks cover what the scenario left out. Twenty opted-in starts and
stops must each still deliver a palette packet; lwIP has eight UDP
sockets, so a palette socket that is not closed runs the pool dry well
within that. And a stream to 230.0.1.64 must arrive as multicast, which
is the 224-231 range this PR moved from unicast.
@barryw
barryw force-pushed the feature/vic-stream-palette branch from 72a4373 to 5dd971c Compare October 4, 2026 21:47
@barryw

barryw commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto master a1a1f44 to clear the conflict. The only conflict was in tests/lib/machine.py: master removed the c64gs-bank-from-address and comal80-cartridge-off-bit entries now that FPGA 126 carries those fixes (#950). I kept that and added only this PR's vic-palette-stream entry.

Retested on the rebased build (Ultimate 64 Elite, FPGA 126, core 1.50): default gate 31/31, and uci-targets with palette 9/9 and palette-stream 13/13. Logs below.

@barryw

barryw commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Default E2E gate, ./run-tests -H 192.168.1.148, against 5dd971c on master a1a1f44 (Ultimate 64 Elite, FPGA 126, core 1.50). Part 1 of 2.

Default gate log, part 1 of 2

============================================================
Ultimate hardware test run
============================================================
     target:     192.168.1.148
     categories: e2e
     profile:    quick (default)
     modes:      overlay
     timeout:    30.0s
     on failure: continue
     health:    answers + health sweep, up to 3 attempts per suite
settings: capture 192.168.1.148 ... OK (197 settings in 18 stores, 0.296s)
machine: resets skip the RAM test ... OK (already set, 0.013s)
the machine keeps its own bus resources ... OK (already set, 0.012s)
the cartridges fitted here are off the IEC bus ... OK (none declared or already off, 0.000s)

============================================================
E2E  lint  (overlay)
============================================================
lint_test: OK (ruff 0.16.5 over tests, run-tests, 0.074s)
lint: OK (0.149s)

============================================================
E2E  registry  (overlay)
============================================================
[01] every suite in the tree is registered in run-tests ... OK (0.000s)
     83 suites under tests/lib, tests/e2e, tests/perf, tests/soak
[02] every registered path exists ... OK (0.000s)
     84 registered paths
[03] every argument template spells only tokens the runner knows ... OK (0.000s)
     tokens: @HOST@, @KERNAL@, @MODE@, @PASS@, @SOAKPROFILE@, @TIMEOUT@
[04] every registered argument is one the suite's parser accepts ... OK (0.374s)
     68 templates, against 3 shared argument helpers
[05] every sys.path line is the shared bootstrap ... OK (0.457s)
     one bootstrap, computed by walking up rather than by counting
[06] -H, -p and -t come from tests/lib/cli.py ... OK (0.548s)
     18 files keep their own, each with a reason
registry_test: OK (84 suites, 1.439s)
registry: OK (1.524s)

============================================================
E2E  transport-usage  (overlay)
============================================================
check_transport_usage: OK (147 files, 1.303s)
transport-usage: OK (1.377s)

============================================================
E2E  esp-depends  (overlay)
============================================================
[01] every cached ESP32 project contributes to the cache key ... OK (0.050s)
     watched: u64ctrl (24 files), wifi/raw_c3 (17 files), wifi/raw_u64 (17 files)
[02] a misspelt directory fails instead of silently hashing nothing ... OK (0.035s)
     wifi/raw_c3/main misspelt as wifi/raw_c3/main_does_not_exist: esp_depends.py exited 1
     a change to any ESP32 project invalidates the cached binaries CI would otherwise hand back unbuilt
esp_depends_test: OK (2 checks, 0.085s)
esp-depends: OK (0.160s)

============================================================
E2E  input-batching  (overlay)
============================================================
[01] a batch of short taps is limited by the event count ... OK (0.003s)
     64 events, 3852 bytes
[02] a batch of long taps is limited by the body size ... OK (0.003s)
     46 events, 4014 bytes
[03] the file browser's 64-keypress field clear fits its requests ... OK (0.001s)
     64 taps as [60, 4]
[04] one oversized event is passed on rather than dropped ... OK (0.000s)
[05] no events make no requests ... OK (0.000s)
input_batching_test: OK (5 checks, 0.036s)
input-batching: OK (0.108s)

============================================================
E2E  runner-policy  (overlay)
============================================================
[01] a bare target is its own C64-side computer ... OK (0.000s)
[02] a cartridge target names the computer it is plugged into ... OK (0.000s)
[03] keyboard input is the computer's, everything else the device's ... OK (0.000s)
[04] a malformed target is rejected before anything starts ... OK (0.000s)
[05] a target's output path is safe to use as a file name ... OK (0.000s)
[06] a cartridge named alone takes the computer the bench declares ... OK (0.000s)
[07] an undeclared cartridge is unchanged, and so is a spelt-out one ... OK (0.000s)
[08] a declaration that says nothing about this device is ignored ... OK (0.000s)
[09] a declaration that names this device and no computer is fatal ... OK (0.000s)
[10] two spellings of the same machines are one target ...      WARN u2 names the same machines as u2@c64u; running it once
OK (0.000s)
[11] the capture reads every store except the clock ... OK (0.000s)
[12] a run that changed nothing writes nothing ... OK (0.000s)
[13] only the items a suite changed are written back ... OK (0.000s)
[14] a value the device will not take back is reported, not hidden ... OK (0.000s)
[15] the profiles are ordered and cumulative ... OK (0.000s)
[16] a suite named with -s runs whatever the profile says ... OK (0.000s)
[17] the manual suites arrive with the deep profile, not before ... OK (0.000s)
[18] the transports widen with depth, and -m still overrides ... OK (0.000s)
[19] an unknown profile is refused, naming the real ones ... OK (0.000s)
[20] move_rows spends page keys before single steps ... OK (0.000s)
[21] the keys sent always add up to the rows asked for ... OK (0.000s)
[22] a zero move sends nothing at all ... OK (0.000s)
[23] the matrix agrees with what the runner selects ... OK (0.000s)
[24] a suite run is counted once per transport swept ... OK (0.000s)
[25] the static view names no duration and no check count ... OK (0.000s)
[26] measured counts come from the run records ... OK (0.003s)
[27] two targets conflict exactly when they share a machine ... OK (0.000s)
[28] a target does not start while a machine it needs is in use ... OK (0.000s)
[29] scheduling every target eventually runs each one exactly once ... OK (0.000s)
[30] a child repeats the parent's work against one target ... OK (0.000s)
[31] every runner option is forwarded to a child or excluded on purpose ... OK (0.000s)
[32] children write into a directory of their own ... OK (0.000s)
[33] concurrent children never tear each other's lines ... OK (0.032s)
[34] every line a child printed reaches the run's output ... OK (0.030s)
[35] the run's status is the worst of its children's ... OK (0.000s)
[36] the gate reads the menu from the device and types into the computer ... OK (0.000s)
[37] a single-device target still sends everything to one host ... OK (0.000s)
[38] only a cartridge target has a computer menu to get out of the way ... OK (0.000s)
[39] a menu that will not open is repaired by resetting the machine ... OK (0.000s)
[40] a menu that will not close is repaired by resetting the machine ... OK (0.000s)
[41] a launcher is descended by reading the cursor, not by pressing blind ... OK (0.000s)
[42] a cartridge target shuts the computer's menu before resetting ... OK (1.001s)
[43] a UI that the reset does not free is reported, not looped on ... OK (0.000s)
[44] no suite builds a REST URL from an unrouted host ... OK (0.000s)
     8 routing forms accepted, tests/lib/rest.py is where the rule lives
[45] a clean run exits 0 ... OK (0.000s)
[46] a suite that needed a second attempt exits EXIT_RETRIED ... OK (0.000s)
[47] a failed suite exits EXIT_SUITE_FAILED ... OK (0.000s)
[48] a recovery with no failure exits EXIT_RECOVERED ... OK (0.000s)
[49] a recovery outranks a retry ... OK (0.000s)
[50] a failure outranks a recovery ... OK (0.000s)
[51] a device that cannot be made healthy outranks a failure ... OK (0.000s)
[52] the statuses are a scale, in severity order ... OK (0.000s)
[53] a device that answers is never recovered ... OK (0.000s)
[54] recovery runs only once the ordinary wait has given up ... OK (0.011s)
     fixture: the device is unhealthy: it is not answering
[55] a recovery that does not bring the device back reports so ... OK (0.072s)
     fixture: the device is unhealthy: it is not answering
[56] a recovery command that exits non-zero is still followed by a probe ... OK (0.012s)
     fixture: the device is unhealthy: it is not answering
[57] a recovery command that hangs is bounded by --recover-timeout ... OK (0.206s)
     fixture: the device is unhealthy: it is not answering
[58] a recovery command the shell cannot find is a failed recovery ... OK (0.071s)
     fixture: the device is unhealthy: it is not answering
[59] no recovery command means no recovery ... OK (0.000s)
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: no --recover-command was given
[60] a suite gives up after --recover-max-per-suite recoveries ... OK (0.136s)
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: this suite has already used its 2 recoveries
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: this suite has already used its 2 recoveries
[61] the per-suite budget starts again at the next suite ... OK (0.069s)
     fixture: the device is unhealthy: it is not answering
[62] the run gives up after --recover-max-total recoveries ... OK (0.139s)
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: the run has already used its 2 recoveries
     fixture: the device is unhealthy: it is not answering
     fixture: not recovering: the run has already used its 2 recoveries
[63] the refusal says which ceiling was reached ... OK (0.071s)
     fixture: the device is unhealthy: it is not answering
[64] a healthy sweep after a failed suite recovers nothing ... OK (0.001s)
     fixture: health: rest=9ms -> OK
[65] a degraded but reachable device is recovered ... OK (0.012s)
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: after recovery, health: rest=9ms -> OK
[66] a device still degraded after recovering is reported, not retried ... OK (0.012s)
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: after recovery, health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: still unhealthy after recovering: it is degraded: ftp
[67] a degraded device is not recovered past its ceiling ... OK (0.012s)
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: after recovery, health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: still unhealthy after recovering: it is degraded: ftp
     fixture: health: rest=9ms ftp=FAIL -> DEGRADED (ftp)
         ftp: refused
     fixture: the device is unhealthy: it is degraded: ftp
     fixture: not recovering: the run has already used its 1 recoveries
[68] --no-health-check takes the sweep out of the decision ... OK (0.000s)
[69] --no-health-check still recovers a device that has gone ... OK (0.070s)
     fixture: the device is unhealthy: it is not answering
[70] -o names the output directory ... OK (0.000s)
[71] --output-dir names the same one ... OK (0.000s)
[72] -j is not accepted ... OK (0.000s)
[73] --jsonl-dir is not accepted ... OK (0.000s)
[74] the old spellings are gone from --help ... OK (0.000s)
[75] the examples use the option the parser accepts ... OK (0.000s)
[76] a child is told where to write with the same option ... OK (0.000s)
[77] a run without --kernal starts the suite without one ... OK (0.000s)
[78] --kernal and --command-interface are passed to the suite ... OK (0.000s)
[79] a child run is given the same KERNAL options ... OK (0.001s)
[80] consecutive resets collapse into one ... OK (0.000s)
[81] a read between two resets does not warrant the second ... OK (0.000s)
[82] a mutation through another object still warrants a reset ... OK (0.000s)
[83] a GET through another object does not warrant a reset ... OK (0.000s)
[84] a mutating call between two resets warrants the second ... OK (0.000s)
[85] force resets even when nothing has changed ... OK (0.000s)
[86] a suite told the device was just reset does not reset again ... OK (0.000s)
[87] a suite not told that still resets ... OK (0.000s)
[88] the default runs a failing suite exactly three times ... OK (0.076s)
[89] --attempts counts executions, so 2 runs it twice ... OK (0.053s)
[90] --attempts 1 and --no-retry both run it exactly once ... OK (0.051s)
[91] a healthy device does not stop a failure being retried ... OK (0.074s)
[92] recoveries do not buy extra executions ... OK (0.072s)
[93] a device that has died by the last attempt is still noticed ... OK (0.074s)
[94] a device that cannot be made healthy ends the run ... OK (0.025s)
[95] a suite killed by a signal says so instead of reading as a failure ... OK (0.026s)
fixture: OK (1.500s)
[96] a health sweep reaches the JSONL with a latency per check ... OK (0.000s)
[97] a suite record carries the profile, attempt and recoveries ... OK (0.000s)
[98] the run record agrees with the exit status ... OK (0.000s)
[99] a sweep with every check passing is healthy ... OK (0.000s)
[100] a failed check makes the sweep degraded and names it ... OK (0.000s)
[101] a skipped check never makes the sweep degraded ... OK (0.000s)
[102] the one-line summary carries every latency and the verdict ... OK (0.000s)
     the recovery command runs on a degraded or unreachable device, never on a suite that merely failed
runner_policy_test: OK (102 checks, 2.498s)
runner-policy: OK (2.586s)

============================================================
E2E  stale-gates  (overlay)
============================================================
[01] a check that passes under an assumed fix is reported stale ... OK (0.002s)
[02] a tag in a per-suite file is found; run.jsonl alone would miss it ... OK (0.003s)
[03] a check that still fails under the assumption is not stale ... OK (0.000s)
[04] one failure among repeats keeps the entry off the stale list ... OK (0.001s)
[05] with no assumption, the gate skips for real and tags nothing ... OK (0.000s)
[06] assuming every fix does not tag a machine that never needed one ... OK (0.000s)
[07] a run with nothing tagged reports no stale gates at all ... OK (0.001s)
stale_gates_test: OK (7 checks, 0.010s)
stale-gates: OK (0.081s)

============================================================
E2E  openapi-validator  (overlay)
============================================================
[01] the core_version field decides which document applies ... OK (0.000s)
[02] a declared profile is used instead of asking the device ... OK (0.000s)
[03] a profile with no document is refused ... OK (0.000s)
[04] a request path finds the call it belongs to ... OK (0.000s)
     8 paths, including the three configs depths
[05] a path the document does not describe matches nothing ... OK (0.000s)
[06] an answer that matches is accepted ... OK (0.569s)
[07] a body missing a required member is refused ... OK (0.000s)
[08] a member of the wrong type is refused ... OK (0.000s)
[09] a status the call does not declare is refused ... OK (0.000s)
[10] the password refusal every call shares is accepted ... OK (0.000s)
[11] JSON where the document promises bytes is refused ... OK (0.000s)
[12] a body that will not parse is refused ... OK (0.000s)
[13] an undescribed call is refused unless the device says so too ... OK (0.000s)
[14] a verb the call does not have is refused ... OK (0.000s)
[15] anything outside /v1 is left alone ... OK (0.000s)
[16] both documents load and describe their own product ... OK (0.143s)
     u2 55 operations, u64 59 operations
openapi_contract_test: OK (16 checks, 0.942s)
openapi-validator: OK (1.031s)

============================================================
E2E  softiec-log  (overlay)
============================================================
[01] a command whose status read crossed a drive reset pairs with the line it wrote ... OK (0.000s)
[02] a reset outside a command's span leaves its status in force ... OK (0.000s)
[03] a drive reset sent during the status read makes the command optional ... OK (0.025s)
[04] the run's collected log gives this device's whole lines after the mark ... OK (0.003s)
[05] a line goes to the operation that needs it, not to an optional one before it ... OK (0.000s)
softiec_log_test: OK (5 checks, 0.072s)
softiec-log: OK (0.145s)

============================================================
E2E  navigation-keys  (overlay)
============================================================
[01] the two values, and an unknown one, resolve to a reading ... OK (0.000s)
[02] a letter is shifted only where the menu folds it back ... OK (0.000s)
[03] the menu cannot be made to read an uppercase letter under wasd ... OK (0.000s)
[04] the setting is read once per host ... OK (0.000s)
[05] a quick-seek prefix is spelt the way the machine will read it ... OK (0.000s)
[06] a name holding a cursor letter is still seeked for ... OK (0.000s)
[07] a popup's button key reaches the button rather than the cursor ... OK (0.000s)
[08] an overlay item is picked by the same spelling ... OK (0.000s)
[09] text typed into a field is never respelt ... OK (0.000s)
[10] a duration is read in every unit the suites use ... OK (0.000s)
[11] a duration that is not one is a usage error, not a traceback ... OK (0.000s)
[12] a duration has to be a length of time, so zero is refused ... OK (0.000s)
     a letter is shifted only where the firmware folds it back, so a field still receives what a suite typed
navigation_test: OK (12 checks, 0.036s)
navigation-keys: OK (0.110s)

============================================================
E2E  ui-backend-parse  (overlay)
============================================================
[01] a row marked by background colour is the selected one ... OK (0.000s)
[02] a blank row never wins, however it is coloured ... OK (0.000s)
[03] a screen with nothing marked says so, rather than guessing ... OK (0.000s)
[04] two entries are separated by the machine's own cursor colour ... OK (0.000s)
[05] a cursor that colours one field still marks its row ... OK (0.000s)
[06] a framed window is read instead of what it covers ... OK (0.000s)
[07] a window narrows what counts as a marking ... OK (0.000s)
[08] row_marks counts only the cells inside the window ... OK (0.000s)
[09] reverse video marks a row where no colour does ... OK (0.000s)
[10] over Telnet, a popup is the one frame inside the browser's ... OK (0.001s)
[11] over Telnet, a disk image's volume row is never taken for the cursor ... OK (0.000s)
[12] over Telnet, panels side by side are each a frame, and a column of one colour has no highlight ... OK (0.000s)
     the rules the parsers were written for, each as one screen
ui_backend_parse_test: OK (12 checks, 0.038s)
ui-backend-parse: OK (0.113s)

============================================================
E2E  keystroke-read-back  (overlay)
============================================================
[01] a field that shows the text is accepted after one typing ... OK (0.000s)
[02] a lost keystroke is typed again, and the retype is reported ... OK (0.154s)
[03] a field that never shows the text fails without being accepted ... OK (0.455s)
[04] the same name already in the listing does not stand in for the field ... OK (0.154s)
[05] a popup key that changes the screen is pressed once ... OK (0.000s)
[06] a popup key that changed nothing is pressed again, and the repeat is reported ... OK (0.152s)
[07] a popup key that never changes the screen fails ... OK (0.455s)
[08] an answer that opens a second popup with the same buttons is not repeated ... OK (0.000s)
[09] a lost quick-seek key is corrected before ENTER, so the named item runs ... OK (0.000s)
[10] a lost ENTER is pressed again, and runs the item once ... OK (0.155s)
[11] a highlight that cannot be read leaves the navigation as it was sent ... OK (0.000s)
[12] a transport that raises instead of reading the highlight still runs the item ... OK (0.000s)
[13] a context menu that opens is opened with one ENTER ... OK (0.000s)
[14] an ENTER the browser never saw is pressed again, and reported ... OK (0.151s)
[15] a screen that changed into something else gets no second ENTER ... OK (0.000s)
[16] a search term that arrives whole is typed once ... OK (0.000s)
[17] a doubled letter lost from a search term is typed again and reported ... OK (0.055s)
[18] a term the field never shows fails ... OK (0.162s)
     string boxes, popup keys, overlay items and search terms read back, against scripted key loss
keystroke_read_back_test: OK (18 checks, 2.009s)
keystroke-read-back: OK (2.079s)

============================================================
E2E  telnet-drain  (overlay)
============================================================
[01] a capture that sent nothing waits only the quiet check ... OK (0.155s)
[02] a key known to draw nothing costs no more than that either ... OK (0.153s)
[03] a redraw that never starts gives up after the first-byte budget ... OK (1.067s)
[04] one burst ends the drain an idle gap after it ... OK (0.204s)
[05] a settled command waits through the pause and takes both bursts ... OK (6.776s)
[06] an ordinary command does not wait for a second burst ... OK (0.660s)
[07] a redraw arriving in pieces is drained as one ... OK (0.313s)
[08] a band header refuses a layout it cannot label ... OK (0.000s)
telnet_drain_test: OK (8 checks, 9.358s)
telnet-drain: OK (9.440s)

============================================================
E2E  ui-backend-smoke  (overlay)
============================================================
     ui-backend-smoke: health: ping=14ms rest=11ms ftp=45ms telnet=34ms ident=22ms dma=9ms heap=5296000B raster=22ms jiffy=33ms -> OK

--- Machine identification
[01] each machine is recognised from its product string ... OK (0.000s)
[02] only the C64 Ultimate opens on a launcher ... OK (0.000s)
[03] the C64 Ultimate searches CommoServe, the others Assembly 64 ... OK (0.000s)
[04] an unrecognised product is refused rather than guessed ... OK (0.000s)
[05] the product is fetched once and then kept ... OK (0.000s)
[06] the firmware version is kept when the caller has it ... OK (0.000s)
[07] every fix in the table names a behaviour and a machine that lacks it ... OK (0.000s)
[08] the table decides which machine skips a tagged check ... OK (0.000s)
[09] a fix no entry lists is one every machine has ... OK (0.000s)
[10] a skipped check names the fix and the machine in its reason ... OK (0.000s)
[11] skip_without_fix answers True only where the check cannot run ... OK (0.000s)
[12] an assumed fix runs the checks it gates, which is how a backport is found ... OK (0.000s)
[13] an assumption list is parsed as a list, and a typo is refused ... OK (0.000s)
--- OK (13 checks, 0.000s)

--- Overlay navigation planner
[14] a unique first letter jumps straight to the item ... OK (0.000s)
[15] a shared first letter extends the prefix rather than walking ... OK (0.000s)
[16] walking wins when the item is already next to the cursor ... OK (0.000s)
[17] the seek lands on the first match and the walk covers the rest ... OK (0.000s)
[18] a prefix never contains a space, which selects rather than seeks ... OK (0.000s)
[19] a plan is never longer than walking from the top ... OK (0.000s)
[20] an upward walk is planned when that is the shorter route ... OK (0.000s)
--- OK (7 checks, 0.000s)

--- Cursor row from the colour plane
[21] a background-marked cursor row is found ... OK (0.000s)
[22] a cursor row marked by foreground colour alone is found ... OK (0.000s)
[23] the first entry is found when it is the selected one ... OK (0.000s)
[24] a cursor that colours only the name field is still found ... OK (0.000s)
[25] a volume header in reverse video is not taken for a cursor ... OK (0.000s)
[26] a form marks a field, not a row, and the field is found ... OK (0.000s)
[27] reverse video still marks the cursor when nothing else does ... OK (0.000s)
[28] a listing of three or more entries says which colour is the cursor ... OK (0.000s)
[29] a background-marked screen teaches no foreground cursor colour ... OK (0.000s)
[30] two entries need the colour the machine was measured to mark with ... OK (0.000s)
--- OK (10 checks, 0.001s)

--- Cursor row under a framed window
[31] a plain listing reports no framed window ... OK (0.000s)
[32] a framed window's own cursor is read, not the browser's ... OK (0.000s)
[33] a cursor further down a framed window is read ... OK (0.000s)
[34] the menu's header rule is not taken for a framed window ... OK (0.000s)
[35] a titled window's cursor is read, not its title ... OK (0.000s)
[36] a window's blank rows never win the cursor colour ... OK (0.000s)
[37] a dialog inside a picker is read, not the picker ... OK (0.000s)
[38] an open window is what the cursor colour is measured on ... OK (0.000s)
[39] a form teaches no cursor colour at all ... OK (0.000s)
[40] a menu beside a highlighted row is read, not the row ... OK (0.000s)
[41] a window with rounded corners is a window too ... OK (0.000s)
[42] an untitled window keeps its first row ... OK (0.000s)
[43] a background-marked window is read on a background-marked browser ... OK (0.000s)
--- OK (13 checks, 0.003s)

--- REST backend, Interface Type = Overlay on HDMI
[44] REST/Overlay: connect, navigate, teardown ... OK (2.691s)
--- OK (1 checks, 2.691s)

--- Telnet backend
[45] Telnet: connect, navigate, teardown ... OK (2.329s)
--- OK (1 checks, 2.329s)

--- REST backend, Interface Type = Freeze
[46] REST/Freeze: connect, navigate, teardown ... OK (2.277s)
--- OK (1 checks, 2.277s)
ui_backend_smoke_test: OK (46 checks, 7.336s)
ui-backend-smoke: OK (7.413s)

============================================================
E2E  readmem-writemem  (overlay)
============================================================
     readmem-writemem: health: ping=7ms rest=10ms ftp=53ms telnet=32ms ident=13ms dma=7ms heap=5284348B raster=29ms jiffy=57ms -> OK
[01] reset C64 to a clean, stable state ... OK (0.043s)
[02] read User Interface Settings config ... OK (0.016s)
     current Interface Type: Freeze

--- bounds: readmem rejects out-of-range parameters before allocating, and max-size reads do not degrade the device
[03] readmem rejects zero length ... OK (0.011s)
[04] readmem rejects negative length ... OK (0.012s)
[05] readmem rejects length one past the 64KB cap ... OK (0.011s)
[06] readmem rejects length far past the cap ... OK (0.010s)
[07] readmem rejects length that overflows a long ... OK (0.014s)
[08] readmem rejects read running past $FFFF ... OK (0.021s)
[09] readmem rejects address above $FFFF ... OK (0.011s)
[10] readmem rejects negative address ... OK (0.010s)
[11] 8 back-to-back max-size reads ($0000, 65536 bytes) all succeed ... OK (1.618s)
[12] 25 unsupported machine:measure calls leave readmem working ... OK (0.515s)
--- OK (10 checks, 2.259s)

--- bad-address: readmem and writemem reject an address that is not valid hex
[13] readmem rejects address='ZZZZ' ... OK (0.011s)
[14] writemem PUT rejects address='ZZZZ' ... OK (0.011s)
[15] writemem POST rejects address='ZZZZ' ... OK (0.037s)
[16] readmem rejects address='0xZZZZ' ... OK (0.015s)
[17] writemem PUT rejects address='0xZZZZ' ... OK (0.010s)
[18] writemem POST rejects address='0xZZZZ' ... OK (0.038s)
[19] readmem rejects address='gggg' ... OK (0.011s)
[20] writemem PUT rejects address='gggg' ... OK (0.010s)
[21] writemem POST rejects address='gggg' ... OK (0.047s)
[22] readmem rejects address='-0' ... OK (0.011s)
[23] writemem PUT rejects address='-0' ... OK (0.011s)
[24] writemem POST rejects address='-0' ... OK (0.052s)
[25] readmem rejects address='+1' ... OK (0.016s)
[26] writemem PUT rejects address='+1' ... OK (0.010s)
[27] writemem POST rejects address='+1' ... OK (0.063s)
[28] readmem rejects address='12GG' ... OK (0.012s)
[29] writemem PUT rejects address='12GG' ... OK (0.017s)
[30] writemem POST rejects address='12GG' ... OK (0.073s)
--- OK (18 checks, 0.467s)

--- bad-debugreg: machine:debugreg rejects a value that is not two hex digits
[31] debugreg rejects value='ZZ' ... OK (0.027s)
[32] debugreg rejects value='0xZZ' ... OK (0.020s)
[33] debugreg rejects value='-0' ... OK (0.022s)
[34] debugreg rejects value='1G' ... OK (0.020s)
[35] debugreg rejects value='1FF' ... OK (0.029s)
[36] debugreg still takes a valid value without changing it ... OK (0.010s)
[37] switch to Overlay on HDMI to probe live background activity ... OK (0.011s)
[38] probe addresses that change on their own while the C64 runs ... OK (5.990s)
     6 address(es) treated as expected live background noise: $00A1, $00A2, $00CD, $00CF, $01F2, $0287
[39] set Interface Type = Freeze ... OK (0.024s)
[40] open menu (Freeze) ... OK (0.103s)
[41] every 4 KB of RAM answers DMA while the machine is frozen ... OK (1.650s)
[42] probe addresses that still change while the machine is frozen ... OK (0.840s)
     0 address(es) change on their own while frozen
[43] write full-range pattern (Freeze) ... OK (0.784s)
[44] read back full range (Freeze) ... OK (0.203s)
[45] menu still open after write+read (Freeze) ... OK (0.015s)
--- OK (15 checks, 9.760s)

--- selfcheck-freeze: write and read both happen in Freeze (screen RAM excluded, the menu redraws its entry rows while open)
     [selfcheck-freeze] ignored ROM mismatches (writes to real ROM are no-ops): 16329
     [selfcheck-freeze] screen ($0400-$07FF) mismatches: 907 (expected, menu owns this range while frozen)
     [selfcheck-freeze] ignored known-live-noise mismatches: 0
     [selfcheck-freeze] color RAM (low nibble) mismatches: 0
     [selfcheck-freeze] RAM mismatches: 0
[46] close menu (Freeze) ... OK (0.037s)
[47] set Interface Type = Overlay on HDMI ... OK (0.012s)
[48] open menu (Overlay on HDMI) ... OK (0.128s)
[49] pause C64 for exact round trip (Overlay on HDMI) ... OK (0.025s)
[50] write full-range pattern (Overlay on HDMI) ... OK (0.792s)
[51] read back full range (Overlay on HDMI) ... OK (0.206s)
[52] resume C64 after exact round trip (Overlay on HDMI) ... OK (0.010s)
[53] menu still open after write+read (Overlay on HDMI) ... OK (0.016s)
--- OK (8 checks, 1.250s)

--- selfcheck-overlay-on-hdmi: write and read both happen in Overlay on HDMI (screen RAM excluded, the menu redraws its entry rows while open)
     [selfcheck-overlay-on-hdmi] ignored ROM mismatches (writes to real ROM are no-ops): 16323
     [selfcheck-overlay-on-hdmi] screen ($0400-$07FF) mismatches: 908 (expected, menu owns this range while frozen)
     [selfcheck-overlay-on-hdmi] ignored known-live-noise mismatches: 0
     [selfcheck-overlay-on-hdmi] color RAM (low nibble) mismatches: 0
     [selfcheck-overlay-on-hdmi] RAM mismatches: 0
[54] close menu (Overlay on HDMI) ... OK (0.047s)
[55] close menu for the screen round trip (Overlay on HDMI) ... OK (0.010s)
[56] pause C64 for the screen round trip (Overlay on HDMI) ... OK (0.013s)
[57] write and read back $0400-$07FF (Overlay on HDMI) ... OK (0.081s)
[58] resume C64 after the screen round trip (Overlay on HDMI) ... OK (0.012s)
--- OK (5 checks, 0.223s)

--- screen-round-trip-overlay-on-hdmi: screen RAM round-trips exactly with no menu open
     [screen-round-trip-overlay-on-hdmi] screen ($0400-$07FF) mismatches: 0 (expected 0)
[59] set Interface Type = Overlay on HDMI ... OK (0.012s)
[60] open menu (Overlay on HDMI) ... OK (0.107s)
[61] write full-range pattern (Overlay on HDMI) ... OK (0.507s)
[62] capture ground truth in Overlay on HDMI mode ... OK (0.196s)
[63] close menu (Overlay on HDMI) ... OK (0.118s)
[64] set Interface Type = Freeze ... OK (0.019s)
[65] open menu (Freeze) ... OK (0.106s)
[66] read full range (Freeze) ... OK (0.196s)
[67] menu still open after cross-mode read (Freeze) ... OK (0.015s)
--- OK (9 checks, 1.322s)

--- overlay-on-hdmi-write_freeze-read: bytes written while Overlay on HDMI must read back the same while Freeze, except screen RAM (menu-owned while frozen)
     [overlay-on-hdmi-write_freeze-read] screen ($0400-$07FF) mismatches: 113 (expected, menu owns this range while frozen)
     [overlay-on-hdmi-write_freeze-read] ignored known-live-noise mismatches: 0
     [overlay-on-hdmi-write_freeze-read] color RAM (low nibble) mismatches: 0
     [overlay-on-hdmi-write_freeze-read] RAM mismatches: 0
[68] close menu (Freeze) ... OK (0.048s)
[69] set Interface Type = Freeze ... OK (0.013s)
[70] open menu (Freeze) ... OK (0.101s)
[71] write full-range pattern (Freeze) ... OK (0.570s)
[72] capture ground truth in Freeze mode ... OK (0.196s)
[73] close menu (Freeze) ... OK (0.112s)
[74] set Interface Type = Overlay on HDMI ... OK (0.020s)
[75] open menu (Overlay on HDMI) ... OK (0.111s)
[76] read full range (Overlay on HDMI) ... OK (0.195s)
[77] menu still open after cross-mode read (Overlay on HDMI) ... OK (0.017s)
--- OK (10 checks, 1.406s)

--- freeze-write_overlay-on-hdmi-read: bytes written while Freeze must read back the same while Overlay on HDMI, except screen RAM (menu-owned while frozen)
     [freeze-write_overlay-on-hdmi-read] screen ($0400-$07FF) mismatches: 113 (expected, menu owns this range while frozen)
     [freeze-write_overlay-on-hdmi-read] ignored known-live-noise mismatches: 0
     [freeze-write_overlay-on-hdmi-read] color RAM (low nibble) mismatches: 0
     [freeze-write_overlay-on-hdmi-read] RAM mismatches: 0
[78] close menu (Overlay on HDMI) ... OK (0.035s)
     restored Interface Type to 'Freeze'
--- OK (1 checks, 0.260s)

--- summary
     bounds: OK
     bad-address: OK
     bad-debugreg: OK
     selfcheck-freeze: OK
     selfcheck-overlay: OK
     screen-round-trip: OK
     overlay-to-freeze: OK
     freeze-to-overlay: OK
readmem_writemem_test: OK (78 checks, 23.1s)
readmem-writemem: OK (23.2s)

============================================================
E2E  menu-screen  (overlay)
============================================================
     menu-screen: health: ping=12ms rest=13ms ftp=55ms telnet=32ms ident=14ms dma=11ms heap=5285228B raster=22ms jiffy=22ms -> OK
[01] open menu ... OK (0.010s)
[02] GET menu_screen contract ... OK (0.017s)
[03] menu_screen renders a real menu ... OK (0.000s)
[04] close menu ... OK (0.010s)
[05] GET menu_screen unavailable ... OK (0.014s)
[06] GET menu_screen auth ... SKIP (--password not supplied, 0.000s)
menu_screen_test: OK (6 checks, 0.863s)
menu-screen: OK (0.947s)

============================================================
E2E  input  (overlay)
============================================================
     input: health: ping=8ms rest=11ms ftp=39ms telnet=35ms ident=13ms dma=7ms heap=5283676B raster=21ms jiffy=27ms -> OK
[01] input snapshot has stable empty response shape ... OK (0.028s)
[02] POST accepts 64 event batch ... OK (0.059s)
[03] bad content-type is rejected without mutation ... OK (0.069s)
[04] missing JSON body is rejected without mutation ... OK (0.064s)
[05] malformed JSON is rejected without mutation ... OK (0.058s)
[06] unknown root field is rejected without mutation ... OK (0.068s)
[07] late invalid event keeps whole batch atomic ... OK (0.077s)
[08] joystick port 1 fire keeps Anykey buttons 2 and 3 released ... OK (0.325s)
[09] joystick port 1 fire2 lights only Anykey button 2, only on port 1 ... OK (0.333s)
[10] joystick port 1 fire3 lights only Anykey button 3, only on port 1 ... OK (0.320s)
[11] joystick port 2 fire keeps Anykey buttons 2 and 3 released ... OK (0.328s)
[12] joystick port 2 fire2 lights only Anykey button 2, only on port 2 ... OK (0.320s)
[13] joystick port 2 fire3 lights only Anykey button 3, only on port 2 ... OK (0.326s)
[14] joystick fire2 on one port and fire3 on the other stay independent ... OK (0.327s)
[15] joystick fire2/fire3 tap auto-releases the POT hardware state ... OK (0.420s)
[16] joystick port 1 up press is visible on CIA reads ... OK (0.110s)
[17] joystick port 1 all inputs and idempotent release are visible on CIA reads ... OK (0.108s)
[18] joystick port 2 diagonal and fire are visible on CIA reads ... OK (0.114s)
[19] joystick partial release is visible on CIA reads ... OK (0.134s)
[20] joystick fire2/fire3 round-trip through REST state and POT hardware ... OK (0.809s)
[21] joystick fire2/fire3 held on port 2 leave port 1's Anykey buttons released ... OK (0.660s)
[22] joystick release in the same batch as a tap on the same input wins ... OK (0.035s)
[23] joystick release_all then press in same batch is visible on CIA reads ... OK (0.116s)
[24] joystick unusual combination is visible on CIA reads ... OK (0.102s)
[25] joystick tap does not release persistent input ... OK (0.320s)
[26] joystick tap auto releases ... OK (0.318s)
[27] invalid joystick batch does not mutate state ... OK (0.140s)
[28] joystick release_all clears both ports, including POT hardware ... OK (0.392s)
[29] machine reset clears keyboard and joystick REST state ... OK (3.114s)
[30] keyboard single-tap batch is consumed by BASIC in order ... OK (0.701s)
[31] keyboard 10 Hz mixed alphabet echo has no missed presses ... OK (2.854s)
[32] keyboard 20 Hz alternating ab echo has no missed presses ... OK (1.556s)
[33] keyboard 5 Hz alternating ab echo has no missed presses ... OK (0.806s)
[34] keyboard single letter reaches the live C64 matrix ... OK (0.155s)
[35] keyboard shifted pair reaches the live C64 matrix ... OK (0.225s)
[36] keyboard batch applies multiple presses atomically ... OK (0.245s)
[37] keyboard ordered batch and idempotent release ... OK (0.068s)
[38] keyboard release_all can be followed by press in same batch ... OK (0.065s)
[39] keyboard accepts eight simultaneous inputs ... OK (0.087s)
[40] keyboard tap does not release persistent key ... OK (0.165s)
[41] keyboard release_all clears state ... OK (0.071s)
[42] keyboard restore tap auto releases ... OK (0.261s)
[43] keyboard special-key taps snapshot correctly and auto release ... OK (2.522s)
[44] keyboard tap is visible in the live hardware snapshot and auto releases ... OK (0.274s)
[45] keyboard cursor-left tap is visible in the live hardware snapshot and auto releases ... OK (0.277s)
[46] keyboard tap batch drains through the live matrix path ... OK (1.472s)
[47] keyboard long repeated tap train drains fully without sticky state ... OK (4.315s)
[48] invalid keyboard batch does not mutate state ... OK (0.061s)
[49] mouse state is detached and empty after release_all ... OK (0.031s)
[50] a mouse button press attaches the mouse and is reported in order ... OK (0.100s)
[51] invalid mouse events are rejected with their error and change nothing ... OK (0.675s)
[52] a tap presses and releases, and a path is queued behind it ... OK (0.510s)
[53] the queue holds 1024 reports and refuses a batch beyond that with 429 ... OK (0.515s)
[54] a request over the JSON value limit is refused with a clear error ... OK (0.029s)
[55] release_all in a batch makes room for what follows it ... OK (5.232s)
[56] release_all detaches the mouse ... OK (0.057s)
[57] a machine reset detaches the mouse and drops its queue ... OK (3.138s)
[58] a joystick held as the menu opens is ignored until released ... OK (5.245s)
input_test: OK (58 checks, 48.6s)
input: OK (48.9s)

@barryw

barryw commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Default E2E gate, ./run-tests -H 192.168.1.148, against 5dd971c on master a1a1f44 (Ultimate 64 Elite, FPGA 126, core 1.50). Part 2 of 2.

Default gate log, part 2 of 2
============================================================
E2E  create-disk-image  (overlay)
============================================================
     create-disk-image: health: ping=9ms rest=10ms ftp=40ms telnet=35ms ident=13ms dma=10ms heap=5281184B raster=22ms jiffy=68ms -> OK

--- preconditions
[01] device reachable ... OK (0.013s)
[02] clear leftovers from an earlier run ... OK (0.120s)
--- OK (2 checks, 0.133s)

--- d64-35
[03] create d64 at /Temp/cdi_d64-35.d64 ... OK (0.051s)
[04] device still answers after create d64 ... OK (0.011s)
[05] /Temp/cdi_d64-35.d64 is 174848 bytes ... OK (0.013s)
--- OK (3 checks, 0.075s)

--- d64-40
[06] create d64 at /Temp/cdi_d64-40.d64 ... OK (0.053s)
[07] device still answers after create d64 ... OK (0.018s)
[08] /Temp/cdi_d64-40.d64 is 196608 bytes ... OK (0.014s)
--- OK (3 checks, 0.085s)

--- d71
[09] create d71 at /Temp/cdi_d71.d71 ... OK (0.093s)
[10] device still answers after create d71 ... OK (0.011s)
[11] /Temp/cdi_d71.d71 is 349696 bytes ... OK (0.014s)
--- OK (3 checks, 0.118s)

--- d81
[12] create d81 at /Temp/cdi_d81.d81 ... OK (0.153s)
[13] device still answers after create d81 ... OK (0.022s)
[14] /Temp/cdi_d81.d81 is 819200 bytes ... OK (0.014s)
--- OK (3 checks, 0.189s)

--- dnp-1
[15] create dnp at /Temp/cdi_dnp-1.dnp ... OK (0.036s)
[16] device still answers after create dnp ... OK (0.013s)
[17] /Temp/cdi_dnp-1.dnp is 65536 bytes ... OK (0.015s)
--- OK (3 checks, 0.065s)

--- dnp-16
[18] create dnp at /Temp/cdi_dnp-16.dnp ... OK (0.151s)
[19] device still answers after create dnp ... OK (0.010s)
[20] /Temp/cdi_dnp-16.dnp is 1048576 bytes ... OK (0.014s)
--- OK (3 checks, 0.175s)

--- repeat
[21] create d64 at /Temp/cdi_d64-35-repeat.d64 ... OK (0.054s)
[22] device still answers after create d64 ... OK (0.012s)
[23] /Temp/cdi_d64-35-repeat.d64 is 174848 bytes ... OK (0.016s)
--- OK (3 checks, 0.201s)
create_disk_image_test: OK (23 checks, 1.072s)
create-disk-image: OK (1.144s)

============================================================
E2E  d81-track-bounds  (overlay)
============================================================
     d81-track-bounds: health: ping=7ms rest=10ms ftp=54ms telnet=35ms ident=14ms dma=7ms heap=5279632B raster=31ms jiffy=24ms -> OK
[01] prepare 1581 and generated D81 fixtures ... OK (3.185s)

--- missing-track-81
[02] mount 80-track D81 on drive b ... OK (0.152s)
[03] device remains reachable after mount ... OK (0.010s)
[04] physical track 81 has the expected result ... OK (4.960s)
     tracks=80, job=$00, io=$00
--- OK (3 checks, 5.133s)

--- track-81
[05] mount 81-track D81 on drive b ... OK (0.155s)
[06] device remains reachable after mount ... OK (0.010s)
[07] physical track 81 has the expected result ... OK (4.978s)
     tracks=81, job=$00, io=$00
--- OK (3 checks, 5.144s)

--- oversized
[08] mount 85-track D81 on drive b ... OK (0.175s)
[09] device remains reachable after mount ... OK (0.010s)
[10] physical track 81 has the expected result ... OK (4.905s)
     tracks=85, job=$00, io=$00
--- OK (3 checks, 5.456s)
d81_track_bounds_test: OK (10 checks, 19.0s)
d81-track-bounds: OK (19.0s)

============================================================
E2E  rest-api-coverage  (overlay)
============================================================
     rest-api-coverage: health: ping=13ms rest=11ms ftp=45ms telnet=42ms ident=15ms dma=7ms heap=5279568B raster=34ms jiffy=54ms -> OK

--- coverage
[01] read the operation list ... OK (59 operations, from 2 OpenAPI document(s), 0.297s)
[02] every operation is exercised or excluded ... OK (58 exercised, 1 excluded, 0.000s)
[03] nothing is classified that the device does not serve ... OK (0.000s)
[04] every operation has a happy path somewhere ... OK (41 here, 9 elsewhere, 8 with none, 0.000s)
     not called: PUT /v1/machine:poweroff - turns the device off, and no test can turn it back on
     negative only: POST /v1/drives/{drive}:load_rom - as the PUT form
     negative only: POST /v1/runners:modplay - as the PUT form
     negative only: POST /v1/runners:run_crt - as the PUT form
     negative only: POST /v1/runners:sidplay - as the PUT form
     negative only: PUT /v1/drives/{drive}:load_rom - a wrong file leaves the drive with a broken ROM until the next reboot, and the tree has no drive ROM to load a good one from
     negative only: PUT /v1/runners:modplay - needs a MOD file and takes over the machine
     negative only: PUT /v1/runners:run_crt - needs a cartridge image and resets the C64
     negative only: PUT /v1/runners:sidplay - needs a SID file and takes over the machine
--- OK (4 checks, 0.297s)

--- preconditions
[05] device reachable ... OK (0.014s)
[06] record the state to restore ... OK (drive a: enabled=True type='1541', 9 settings, 0.167s)
--- OK (2 checks, 0.181s)

--- operations
     79 cases x3, concurrent, 3 workers
[07] GET /v1/version - returns a version x3 ... OK (0.000s)
[08] GET /v1/info - names product and firmware x3 ... OK (0.000s)
[09] GET /v1/info - reports the interface MAC addresses x3 ... OK (0.000s)
[10] GET /v1/info - names the commit it was built from x3 ... OK (0.000s)
[11] GET /v1/help - answers for a known command x3 ... OK (0.000s)
[12] GET /v1/help - refuses a missing command x3 ... OK (0.000s)
[13] GET /v1/configs - lists categories x3 ... OK (0.000s)
[14] GET /v1/configs/{category} - reads one category x3 ... OK (0.000s)
[15] GET /v1/configs/{category}/{item} - reads one item x3 ... OK (0.000s)
[16] GET /v1/configs/{category} - refuses an unknown category x3 ... OK (0.000s)
[17] PUT /v1/configs/{category}/{item} - writing the current value back is a no-op x3 ... OK (0.000s)
[18] PUT /v1/configs/{category}/{item}/{value} - the value-in-path form sets the same item x3 ... OK (0.000s)
[19] PUT /v1/configs/{category}/{item} - refuses an unknown category x3 ... OK (0.000s)
[20] POST /v1/configs - the JSON form sets an item x3 ... OK (0.000s)
[21] POST /v1/configs - refuses an unknown category x3 ... OK (0.000s)
[22] PUT /v1/configs:save_to_flash - save then load leaves the value x3 ... OK (0.000s)
[23] PUT /v1/configs:load_from_flash - covered by the save case above x3 ... OK (0.000s)
[24] PUT /v1/configs/{category}:save_to_flash - save then load leaves the value x3 ... OK (0.000s)
[25] PUT /v1/configs/{category}:load_from_flash - covered by the save case above x3 ... OK (0.000s)
[26] PUT /v1/configs/{category}:reset_to_default - resets one category and the values restore x3 ... OK (0.000s)
[27] PUT /v1/configs:reset_to_default - resets everything, needs --allow-global-reset x3 ... SKIP (resets every category at once; pass --allow-global-reset, 0.000s)
[28] GET /v1/files/{path}:info - describes /Temp x3 ... OK (0.000s)
[29] GET /v1/files/{path}:info - reports a missing path as absent x3 ... OK (0.000s)
[30] PUT /v1/files/{path}:create_d64 - refuses an unwritable path x3 ... OK (0.000s)
[31] PUT /v1/files/{path}:create_d71 - refuses an unwritable path x3 ... OK (0.000s)
[32] PUT /v1/files/{path}:create_d81 - refuses an unwritable path x3 ... OK (0.000s)
[33] PUT /v1/files/{path}:create_dnp - refuses an unwritable path x3 ... OK (0.000s)
[34] GET /v1/machine:readmem - returns the length asked for x3 ... OK (0.000s)
[35] GET /v1/machine:readmem - refuses a zero length x3 ... OK (0.000s)
[36] GET /v1/machine:heap - reports free within total x3 ... OK (0.000s)
[37] GET /v1/machine:measure - answers, 501 without bus measurement x3 ... OK (0.000s)
[38] GET /v1/machine:input - reports the input state x3 ... OK (0.000s)
[39] GET /v1/machine:debugreg - reads the register x3 ... OK (0.000s)
[40] PUT /v1/machine:debugreg - writing the held value back is a no-op x3 ... OK (0.000s)
[41] GET /v1/machine:menu_screen - answers 404 with no menu open x3 ... OK (0.000s)
[42] PUT /v1/machine:writemem - the byte written is the byte read back x3 ... OK (0.000s)
[43] POST /v1/machine:writemem - the uploaded block reads back x3 ... OK (0.000s)
[44] PUT /v1/machine:writemem - refuses a missing data argument x3 ... OK (0.000s)
[45] POST /v1/machine:input - refuses an empty event list x3 ... OK (0.000s)
[46] PUT /v1/machine:pause - pauses and resumes (status only) x3 ... OK (0.000s)
[47] PUT /v1/machine:resume - covered by the pause case above x3 ... OK (0.000s)
[48] PUT /v1/machine:menu_button - opens the menu and closes it again x3 ... OK (0.000s)
[49] PUT /v1/machine:reset - the C64 comes back to READY x3 ... OK (0.000s)
[50] PUT /v1/machine:reboot - the C64 comes back to READY x3 ... OK (0.000s)
[51] GET /v1/drives - lists both drive slots x3 ... OK (0.000s)
[52] PUT /v1/drives/{drive}:on - flips the drive and the listing agrees x3 ... OK (0.000s)
[53] PUT /v1/drives/{drive}:off - covered by the on/off case above x3 ... OK (0.000s)
[54] PUT /v1/drives/{drive}:mount - the listing shows the image x3 ... OK (0.000s)
[55] PUT /v1/drives/{drive}:unlink - keeps the disk, drops the file x3 ... OK (0.000s)
[56] PUT /v1/drives/{drive}:remove - clears the mounted image x3 ... OK (0.000s)
[57] POST /v1/drives/{drive}:mount - an uploaded image mounts x3 ... OK (0.000s)
[58] PUT /v1/drives/{drive}:set_mode - setting the current mode is a no-op x3 ... OK (0.000s)
[59] PUT /v1/drives/{drive}:reset - resets the drive (status only) x3 ... OK (0.000s)
[60] PUT /v1/drives/{drive}:reset - refuses an unknown drive letter x3 ... OK (0.000s)
[61] PUT /v1/drives/{drive}:mount - refuses a missing image x3 ... OK (0.000s)
[62] PUT /v1/drives/{drive}:load_rom - refuses a missing ROM file x3 ... OK (0.000s)
[63] POST /v1/drives/{drive}:load_rom - refuses a request with no body x3 ... OK (0.000s)
[64] PUT /v1/drives/{drive}:set_mode - refuses an unknown mode x3 ... OK (0.000s)
[65] PUT /v1/drives/{drive}:remove - refuses an unknown drive letter x3 ... OK (0.000s)
[66] PUT /v1/drives/{drive}:on - refuses an unknown drive letter x3 ... OK (0.000s)
[67] PUT /v1/drives/{drive}:off - refuses an unknown drive letter x3 ... OK (0.000s)
[68] PUT /v1/drives/{drive}:unlink - refuses an unknown drive letter x3 ... OK (0.000s)
[69] PUT /v1/runners:sidplay - refuses a missing file x3 ... OK (0.000s)
[70] POST /v1/runners:sidplay - refuses a request with no body x3 ... OK (0.000s)
[71] PUT /v1/runners:modplay - refuses a missing file x3 ... OK (0.000s)
[72] POST /v1/runners:modplay - refuses a request with no body x3 ... OK (0.000s)
[73] PUT /v1/runners:load_prg - refuses a missing file x3 ... OK (0.000s)
[74] POST /v1/runners:load_prg - refuses a request with no body x3 ... OK (0.000s)
[75] PUT /v1/runners:run_prg - refuses a missing file x3 ... OK (0.000s)
[76] POST /v1/runners:run_prg - refuses a request with no body x3 ... OK (0.000s)
[77] PUT /v1/runners:run_crt - refuses a missing file x3 ... OK (0.000s)
[78] POST /v1/runners:run_crt - refuses a request with no body x3 ... OK (0.000s)
[79] PUT /v1/streams/{stream}:start - starts and stops the debug stream x3 ... OK (0.000s)
[80] PUT /v1/streams/{stream}:stop - covered by the start case above x3 ... OK (0.000s)
[81] PUT /v1/streams/{stream}:start - refuses an unknown stream x3 ... OK (0.000s)
[82] PUT /v1/streams/{stream}:stop - refuses an unknown stream x3 ... OK (0.000s)
[83] protocol: an unknown route is 404 x3 ... OK (0.000s)
[84] protocol: a command asked for with the wrong method does not run x3 ... OK (0.000s)
[85] protocol: an unauthenticated read is refused x3 ... SKIP (no password is set on this device, 0.000s)
--- SKIP (79 checks, 24.9s)
rest_api_coverage_test: OK (85 checks, 25.4s)
rest-api-coverage: OK (25.5s)

============================================================
E2E  openapi-contract  (overlay)
============================================================
     openapi-contract: health: ping=7ms rest=22ms ftp=43ms telnet=33ms ident=13ms dma=15ms heap=5278988B raster=23ms jiffy=28ms -> OK
[01] u2 document is valid OpenAPI 3.1 ... OK (0.492s)
     44 paths, 33 schemas
[02] u64 document is valid OpenAPI 3.1 ... OK (0.513s)
     47 paths, 35 schemas
     Ultimate 64 Elite is described by the u64 document
[03] the device serves its own OpenAPI document ... OK (0.471s)
     148565 bytes, byte for byte the committed u64 document
[04] the device serves the API explorer ... OK (0.092s)
[05] a client generates from the u64 document ... OK (0.845s)
     package ultimate_64_rest_api_client
[06] the generated client reads the interface version ... OK (0.097s)
     version 0.1
[07] the generated client reads memory and agrees with the raw call ... OK (0.023s)
     $D020 reads fef6
[08] GET /v1/version matches the document ... OK (0.011s)
[09] GET /v1/info matches the document ... OK (0.022s)
[10] GET /v1/machine:heap matches the document ... OK (0.011s)
[11] GET /v1/drives matches the document ... OK (0.028s)
[12] GET /v1/configs matches the document ... OK (0.015s)
[13] GET /v1/configs/Drive%20A%20Settings matches the document ... OK (0.015s)
[14] GET /v1/configs/Drive%20A%20Settings/* matches the document ... OK (0.064s)
[15] GET /v1/machine:readmem matches the document ... OK (0.011s)
[16] GET /v1/machine:menu_screen matches the document ... OK (0.011s)
     HTTP 404
[17] a rejected call matches the document ... OK (0.014s)
[18] a refused call matches the document ... SKIP (no network password is configured on this device, 0.000s)
openapi_contract_test: OK (18 checks, 3.747s)
openapi-contract: OK (3.891s)

============================================================
E2E  browser-long-filename  (overlay)
============================================================
     browser-long-filename: health: ping=8ms rest=13ms ftp=49ms telnet=35ms ident=18ms dma=9ms heap=5278988B raster=25ms jiffy=66ms -> OK
[01] reset the machine to a clean starting state ... OK (0.557s)
[02] seed long-name fixture for rename ... OK (0.813s)
[03] browser rename on long filename ... OK (4.254s)
[04] reseed long-name fixture for mount ... OK (0.907s)
[05] browser mount on long filename ... OK (4.391s)
browser_long_filename_test: OK (5 checks, 11.4s)
browser-long-filename: OK (12.5s)

============================================================
E2E  ftp-client  (overlay)
============================================================
     ftp-client: health: ping=7ms rest=10ms ftp=45ms telnet=32ms ident=13ms dma=8ms heap=5278880B raster=22ms jiffy=25ms -> OK
     inferred --ftp-advertised-host 192.168.1.78
     controlled FTP server on 0.0.0.0:2121 (advertised 192.168.1.78)
[I 2026-10-04 17:54:59] concurrency model: async
[I 2026-10-04 17:54:59] masquerade (NAT) address: 192.168.1.78
[I 2026-10-04 17:54:59] passive ports: 30000->30019
[I 2026-10-04 17:54:59] >>> starting FTP server on 0.0.0.0:2121, pid=86510 <<<
     server root: /var/folders/52/5656d2050tz7nvc4b1m1tfqw0000gn/T/u64ftp-5wwu263k (marker E2E-1791150899)
[01] GET /v1/version reachable ... OK (0.1, 0.032s)
[02] menu closed -> menu_screen 404 ... OK (0.039s)
[03] menu_button opens menu ... OK (0.100s)
[04] menu_screen returns 2000-byte matrix ... OK (2000 bytes, 0.016s)
[05] input release_all works ... OK (0.018s)
[06] menu closes from anywhere ... OK (0.341s)
[07] remove any stale hosts left by a prior run ... OK (0 removed, 1.848s)

--- stage: smoke
[08] create FTP host 'E2EFTP0900b68s' through the New Host form ... OK (7.852s)
[09] new alias appears under /ftp ... OK (1.829s)
[10] enter host: server sees login + TYPE I + LIST ... [I 2026-10-04 17:55:14] 192.168.1.62:52432-[] FTP session opened (connect)
[I 2026-10-04 17:55:14] 192.168.1.62:52432-[u64e2e] USER 'u64e2e' logged in.
OK (PASS,TYPE,LIST, 2.789s)
[11] root listing shows fixture entries ... OK (README,DIRA,SMALL, 0.019s)
[12] open README.TXT -> RETR (52 bytes) ... [I 2026-10-04 17:55:16] 192.168.1.62:52432-[u64e2e] RETR /private/var/folders/52/5656d2050tz7nvc4b1m1tfqw0000gn/T/u64ftp-5wwu263k/README.TXT completed=1 bytes=52 seconds=0.001
OK (52 bytes, 1.943s)
[13] remove host and confirm it disappears ... [I 2026-10-04 17:55:19] 192.168.1.62:52432-[u64e2e] FTP session closed (disconnect).
OK (5.540s)
--- OK (6 checks, 20.0s)

--- cleanup
     preserved: nothing

--- summary
     Phase    Operation              Result             Commands       Fixture/Notes
     ------------------------------------------------------------------------------
     smoke    create host            OK                                E2EFTP0900b68s
     smoke    list host              OK                                E2EFTP0900b68s
     smoke    enter/LIST             OK                 USER/PASS/TYPE E2EFTP0900b68s
     smoke    root entries           OK                 LIST           README/DIRA/SMALL
     smoke    RETR README            OK                 RETR           README.TXT 52 bytes
     smoke    remove host            OK                                E2EFTP0900b68s
     ------------------------------------------------------------------------------
     Total REST requests   : 125
     Total menu snapshots  : 56
     Total keyboard events : 87
     Total FTP commands    : 24
     Created host alias    : (removed)
     Remote root path      : /var/folders/52/5656d2050tz7nvc4b1m1tfqw0000gn/T/u64ftp-5wwu263k
     Cleanup status        : clean
Exception in thread Thread-1 (serve_forever):
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 1041, in _bootstrap_inner
    self.run()
    ~~~~~~~~^^
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 992, in run
    self._target(*self._args, **self._kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/barry/Library/Python/3.13/lib/python/site-packages/pyftpdlib/servers.py", line 254, in serve_forever
    self.ioloop.loop(timeout, blocking)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "/Users/barry/Library/Python/3.13/lib/python/site-packages/pyftpdlib/ioloop.py", line 376, in loop
    poll(timeout)
    ~~~~^^^^^^^^^
  File "/Users/barry/Library/Python/3.13/lib/python/site-packages/pyftpdlib/ioloop.py", line 741, in poll
    kevents = self._kqueue.control(
        None, _len(self.socket_map), timeout
    )
OSError: [Errno 9] Bad file descriptor
ftp_client_test: OK (6 operation(s), 24.4s)
ftp-client: OK (24.7s)

============================================================
E2E  prg-load-path-trim  (overlay)
============================================================
     prg-load-path-trim: health: ping=9ms rest=21ms ftp=43ms telnet=33ms ident=14ms dma=9ms heap=5278764B raster=21ms jiffy=31ms -> OK

--- Ultimate 64 PRG load path trim
     host: 192.168.1.148
     PUT file path: /Temp/rest-prg-path-trim-target-example.prg
     POST upload name: rest-prg-path-trim-upload-example.prg

--- 1. Capture Current Configuration
     Temp Auto Cleanup: Enabled
     Temp Subfolders: Enabled

--- 2. Configure Temp Settings
[01] set Temp Auto Cleanup to Enabled ... OK (0.011s)
[02] set Temp Subfolders to Enabled ... OK (0.012s)
--- OK (2 checks, 0.023s)

--- 3. Prepare PRG Fixture
[03] upload PUT fixture rest-prg-path-trim-target-example.prg ... OK (0.113s)
--- OK (1 checks, 0.114s)

--- 4. Validate PUT Endpoints
[04] exercise PUT runners:load_prg ... OK (0.396s)
     PUT load_prg boot-cart name: ...T-EXAMPLE
[05] exercise PUT runners:run_prg ... OK (1.778s)
     PUT run_prg boot-cart name: ...T-EXAMPLE
--- OK (2 checks, 2.298s)

--- 5. Validate POST Endpoints
[06] exercise POST runners:load_prg ... OK (0.445s)
     POST load_prg boot-cart name: ...D-EXAMPLE
[07] exercise POST runners:run_prg ... OK (1.805s)
     POST run_prg boot-cart name: ...D-EXAMPLE
--- OK (2 checks, 2.830s)

--- 6. Validate a P00 wrapper through the same runner
[08] upload P00 fixture rest-prg-wrap.p00 ... OK (0.114s)
[09] exercise PUT runners:load_prg on a P00 wrapper ... OK (0.396s)
     P00 boot-cart name: WRAPPED PROGRAM
[10] the program behind the header is what was loaded ... OK (0.013s)
--- OK (3 checks, 0.593s)
prg_load_path_trim_test: OK (10 checks, 6.540s)

--- restore User Interface Settings
[11] set Temp Auto Cleanup to Enabled ... OK (0.017s)
[12] set Temp Subfolders to Enabled ... OK (0.011s)
prg-load-path-trim: OK (7.498s)

============================================================
E2E  cfg-single-group  (overlay)
============================================================
     cfg-single-group: health: ping=7ms rest=11ms ftp=55ms telnet=33ms ident=13ms dma=7ms heap=5280400B raster=26ms jiffy=24ms -> OK
[01] load a one-group CFG file through the browser ... OK (5.863s)
cfg_single_group_test: OK (1 checks, 6.401s)
cfg-single-group: OK (6.941s)

============================================================
E2E  cfg-loader-log  (overlay)
============================================================
[01] an uninterrupted log gives the stores it names ... OK (0.000s)
[02] a log with no loader lines in it reports nothing ... OK (0.000s)
[03] a spliced effectuated record yields no store name ... OK (0.000s)
[04] both halves of a spliced effectuated record are reported ... OK (0.000s)
[05] a record appended to another task's line is reported, not read ... OK (0.000s)
[06] a spliced clean record yields no store name and is reported ... OK (0.000s)
cfg_loader_log_test: OK (6 checks, 0.037s)
cfg-loader-log: OK (0.105s)

============================================================
E2E  printer  (overlay)
============================================================
     printer: health: ping=7ms rest=10ms ftp=47ms telnet=34ms ident=14ms dma=9ms heap=5280396B raster=24ms jiffy=33ms -> OK
[01] REST reachable at 192.168.1.148 ... OK (0.017s)
[02] reset before run ... OK (0.092s)
[03] load printer_e2e.prg ... OK (914 bytes, 0.001s)
[04] capture original Printer Settings ... OK (0.155s)
[05] print epson/bitmap: apply settings + run PRG ... OK (phase=printer closed heartbeat=4, 2.672s)
[06] print epson/bitmap: Flush/Eject via on-screen menu ... OK (4.203s)
     the flush held the menu for 2.4s

--- restoring original Printer Settings
     IEC printer: Disabled
     Bus ID: 4
     Output file: /Usb0/printer
     Output type: PNG B&W
     Ink density: Medium
     Page top margin (default is 5): 5
     Page height (default is 60): 60
     Emulation: Commodore MPS
     Commodore charset: USA/UK
     Epson charset: Basic
     IBM table 2: International 1

--- summary
     epson      bitmap PASS_NO_VERIFY       /USB1/printer/e2e-eb3620
printer_test: OK (1 combination(s), 8.096s)
printer: OK (8.203s)

============================================================
E2E  usb-mouse  (overlay)
============================================================
     usb-mouse: health: ping=7ms rest=17ms ftp=41ms telnet=33ms ident=19ms dma=7ms heap=5275936B raster=21ms jiffy=63ms -> OK
     saved settings: Mouse Mode='Mouse', Mouse Sensitivity=8, Mouse Acceleration='Off', Mouse Wheel Sensitivity=8, Mouse Wheel Direction='Normal', Joystick Swapper='Normal', Menu Mouse Navigation='Enabled', System Mode='NTSC'
     quick profile: move, precision, buttons, wheel-micromys, cursor-mode
[01] moves [(30, 20)] land at (30, 20) ... OK (0.854s)
     x=30 y=20 step x(0, 30) y(0, 20) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$54/$70 lines=$00 frames=25 moved=1
[02] moves [(-80, -65)] land at (-80, -65) ... OK (0.605s)
     x=-80 y=-65 step x(-31, 0) y(-31, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$78/$1A lines=$00 frames=30 moved=3
[03] moves [(63, -63), (-20, 40)] land at (43, -23) ... OK (0.619s)
     x=43 y=-23 step x(-20, 31) y(-31, 31) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$6E/$46 lines=$00 frames=30 moved=4
[04] 120 reports of 1,-1 move a frame each ... OK (3.248s)
     x=120 y=-120 step x(0, 1) y(-1, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$08/$08 lines=$00 frames=188 moved=120
[05] 120 reports of -2,2 move a frame each ... OK (3.250s)
     x=-240 y=240 step x(-2, 0) y(0, 2) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$38/$38 lines=$00 frames=188 moved=120
[06] all 8 button states ... OK (1.660s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=4 middle=2 right=1] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=93 moved=0
[07] a REST tap of each button, and of all three, is one press each ... OK (0.977s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=2 middle=2 right=2] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=56 moved=0
[08] sensitivity 3: 2 up and 1 down detents, slowly, give 3 pulses each and leave the position ... OK (2.008s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=6 down=3 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=118 moved=0
[09] 10 detents as fast as the host polls queue and all pulse ... OK (1.380s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=10 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=81 moved=0
[10] Cursor, sensitivity 2: wheel -2/0 types cursor keys {'up': 0, 'down': 8, 'left': 0, 'right': 0} ... OK (1.122s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[down=8] keys held=0 [0.7=8] pot=$18/$18 lines=$00 frames=66 moved=0
[11] Cursor, sensitivity 2: wheel 0/1 types cursor keys {'up': 0, 'down': 0, 'left': 3, 'right': 0} ... OK (0.905s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[left=3] keys held=0 [0.2=3 6.4=3] pot=$18/$18 lines=$00 frames=54 moved=0
[12] Cursor: [(8, 0)] types {'right': 2} and leaves the pointer ... OK (0.488s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[right=2] keys held=0 [0.2=2] pot=$18/$18 lines=$00 frames=28 moved=0
[13] Cursor: [(-12, 0)] types {'left': 3} and leaves the pointer ... OK (0.539s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[left=3] keys held=0 [0.2=3 6.4=3] pot=$18/$18 lines=$00 frames=32 moved=0
[14] Cursor: [(0, 8)] types {'down': 2} and leaves the pointer ... OK (0.470s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[down=2] keys held=0 [0.7=2] pot=$18/$18 lines=$00 frames=28 moved=0
[15] Cursor: [(0, -2)] types nothing and leaves the pointer ... OK (0.459s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=26 moved=0
[16] Cursor: [(0, -2), (0, -2)] types {'up': 1} and leaves the pointer ... OK (0.525s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[up=1] keys held=0 [0.7=1 6.4=1] pot=$18/$18 lines=$00 frames=31 moved=0
[17] Cursor: [(1, 0), (1, 0), (1, 0)] types nothing and leaves the pointer ... OK (0.589s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[] keys held=0 [] pot=$18/$18 lines=$00 frames=34 moved=0
[18] Cursor: [(1, 0), (1, 0), (1, 0), (1, 0)] types {'right': 1} and leaves the pointer ... OK (0.653s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[right=1] keys held=0 [0.2=1] pot=$18/$18 lines=$00 frames=38 moved=0
[19] Cursor: motion far faster than the keys can be typed stops soon after the mouse ... OK (1.598s)
     keys kept coming for 0.98s after the mouse stopped: x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[right=20] keys held=0 [0.2=20] pot=$18/$18 lines=$00 frames=95 moved=0
[20] Cursor: turning the other way drops the keys still waiting ... OK (0.769s)
     x=0 y=0 step x(0, 0) y(0, 0) held=none presses[left=0 middle=0 right=0] wheel up=0 down=0 port2=none [] cursor[left=1 right=5] keys held=0 [0.2=6 6.4=1] pot=$18/$18 lines=$00 frames=45 moved=0
mouse_test: OK (20 checks, 25.5s)
usb-mouse: OK (25.6s)

============================================================
E2E  ftp-server  (overlay)
============================================================
     ftp-server: health: ping=7ms rest=20ms ftp=43ms telnet=33ms ident=13ms dma=26ms heap=5274344B raster=22ms jiffy=23ms -> OK

--- a name the listing reports can address the file it names
[01] a 40-character name is listed unchanged ... OK (0.066s)
[02] that file is removed by the name the listing reported ... OK (0.068s)
[03] a 100-character name is listed unchanged ... OK (0.059s)
     100 characters survived the listing
[04] SIZE answers for the long name the listing reported ... OK (0.043s)
[05] that file is removed by the name the listing reported ... OK (0.061s)
--- OK (5 checks, 0.386s)
ftp_server_test: OK (5 checks, 0.391s)
ftp-server: OK (0.458s)

============================================================
E2E  assembly64  (overlay)
============================================================
     assembly64: health: ping=7ms rest=18ms ftp=39ms telnet=34ms ident=13ms dma=20ms heap=5268136B raster=22ms jiffy=21ms -> OK
     this machine searches Assembly 64, from its task menu

--- the form opens from the menu and closes again
[01] open the Assembly 64 Query Form ... OK (1.355s)
[02] the form leaves cleanly with RUN/STOP ... OK (0.344s)
--- OK (2 checks, 1.791s)

--- a real query is sent to Assembly 64
[03] open the form and enter the first field ... OK (0.950s)
     the query term goes in 'Name:', the form's first field
[04] the typed term lands in the first field ... OK (0.688s)
[05] the service answers and the results match what was asked for ... OK (1.020s)
     20 result rows mention 'turrican'
--- OK (3 checks, 3.130s)

--- dropdown selections preserve the query form (#863)
[06] the form draws fields, and at least one of them is a dropdown ... OK (8.429s)
     13 field(s) drawn: Name:, Group:, Handle:, Event:, Repo:, Category:, Subcat:, Rating:, Type:, Date:, Latest:, Sort:, Order:
     4 free text: Name:, Group:, Handle:, Event:
     9 dropdown: Repo:, Category:, Subcat:, Rating:, Type:, Date:, Latest:, Sort:, Order:
[07] populate the free-text fields ... OK (3.804s)
[08] Repo: +/- preserves existing fields ... OK (0.724s)
[09] cancelling Repo: preserves existing fields ... OK (0.595s)
[10] confirming Repo: preserves its value and all other fields ... OK (2.067s)
[11] Category: +/- preserves existing fields ... OK (0.920s)
[12] cancelling Category: preserves existing fields ... OK (0.507s)
[13] confirming Category: preserves its value and all other fields ... OK (2.097s)
[14] Subcat: +/- preserves existing fields ... OK (1.014s)
[15] cancelling Subcat: preserves existing fields ... OK (0.561s)
[16] confirming Subcat: preserves its value and all other fields ... OK (2.203s)
[17] Rating: +/- preserves existing fields ... OK (0.920s)
[18] cancelling Rating: preserves existing fields ... OK (0.563s)
[19] confirming Rating: preserves its value and all other fields ... OK (2.101s)
[20] Type: +/- preserves existing fields ... OK (1.038s)
[21] cancelling Type: preserves existing fields ... OK (0.520s)
[22] confirming Type: preserves its value and all other fields ... OK (2.182s)
[23] Date: +/- preserves existing fields ... OK (1.050s)
[24] cancelling Date: preserves existing fields ... OK (0.609s)
[25] confirming Date: preserves its value and all other fields ... OK (2.088s)
[26] Latest: +/- preserves existing fields ... OK (0.979s)
[27] cancelling Latest: preserves existing fields ... OK (0.594s)
[28] confirming Latest: preserves its value and all other fields ... OK (2.046s)
[29] Sort: +/- preserves existing fields ... OK (0.937s)
[30] cancelling Sort: preserves existing fields ... OK (0.509s)
[31] confirming Sort: preserves its value and all other fields ... OK (2.021s)
[32] Order: +/- preserves existing fields ... OK (1.256s)
[33] cancelling Order: preserves existing fields ... OK (0.515s)
[34] confirming Order: preserves its value and all other fields ... OK (2.022s)
--- OK (29 checks, 45.3s)

--- the menu button must work from inside the edit field
[35] open the form and enter the edit field ... OK (0.874s)
[36] the menu button closes the menu from inside the field ... OK (0.089s)
[37] the menu opens again, so the UI task left string_edit ... OK (0.096s)
--- OK (3 checks, 1.505s)

--- an aborted edit must not wedge the form
[38] open the form, type into a field, then abort ... OK (1.516s)
[39] the form is still usable after the abort ... OK (0.016s)
--- OK (2 checks, 2.000s)

--- over-long and empty input
[40] type more than the field accepts ... OK (1.896s)
[41] an empty query is refused rather than sent ... OK (1.777s)
[42] the warning is dismissed and the form is still usable ... OK (0.298s)
--- OK (3 checks, 4.348s)

--- a user mashing keys while the form is busy
[43] submit a query and hammer keys while it runs ... OK (4.596s)
[44] the device is still answering after the mashing ... OK (0.009s)
--- OK (2 checks, 5.049s)

--- opening and abandoning the form repeatedly
[45] open and abandon the form three times ... OK (2.855s)
[46] the menu button closes and reopens the form three times ... OK (3.446s)
--- OK (2 checks, 6.609s)
assembly64_test: OK (46 checks, 70.2s)
assembly64: OK (70.3s)

============================================================
E2E  freezer-audio  (overlay)
============================================================
     freezer-audio: health: ping=7ms rest=11ms ftp=42ms telnet=33ms ident=14ms dma=9ms heap=5234684B raster=22ms jiffy=31ms -> OK
[01] entering the freezer menu is silent on a silent machine ... OK (0.738s)
     entry peak RMS 0.001
[02] a SID tune reaches the audio stream ... OK (2.018s)
     baseline RMS 0.107
[03] entering the freezer menu is silent while a tune plays ... OK (0.138s)
     entry peak RMS 0.106 against a 0.107 tune
[04] the freezer menu silences the machine ... OK (0.402s)
     frozen RMS 0.000, under the 0.011 bound
[05] closing the freezer menu gives the sound back ... OK (1.298s)
     resumed RMS 0.107
[06] playing a SID from the freezer menu gives the sound back ... OK (2.551s)
     relaunched RMS 0.107
freezer_audio_test: OK (6 checks, 8.202s)
freezer-audio: OK (8.283s)

TEARDOWN (overlay): release input ... OK (0.015s)
TEARDOWN (overlay): close active menu UI ... OK (0.080s)
TEARDOWN (overlay): reset machine ... OK (0.203s)

settings: restore 192.168.1.148 ... OK (unchanged, 0.543s)

============================================================
Summary
============================================================
     OK   e2e overlay    313s (31 ok)
     slowest: assembly64 70.3s, input 48.9s, usb-mouse 25.6s
     313s in suites, 17.5s in health checks, the UI-state gate and teardown
     331s total

OK  all 31 suite runs passed.

@barryw

barryw commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

uci-targets, same build:

uci-targets log

============================================================
Ultimate hardware test run
============================================================
     target:     192.168.1.148
     categories: e2e
     profile:    quick (default)
     modes:      overlay
     timeout:    30.0s
     on failure: continue
     health:    answers + health sweep, up to 3 attempts per suite
settings: capture 192.168.1.148 ... OK (197 settings in 18 stores, 0.625s)
machine: resets skip the RAM test ... OK (already set, 0.058s)
the machine keeps its own bus resources ... OK (already set, 0.025s)
the cartridges fitted here are off the IEC bus ... OK (none declared or already off, 0.000s)

============================================================
E2E  uci-targets  (overlay)
============================================================
     uci-targets: health: ping=7ms rest=12ms ftp=104ms telnet=65ms ident=14ms dma=7ms heap=5242444B raster=194ms jiffy=113ms -> OK
[01] reset the C64 so the command interface starts idle ... OK (3.047s)
[02] read 'C64 and Cartridge Settings' ... OK (0.018s)
     current: {'Command Interface': 'Enabled', 'RAM Expansion Unit': 'Disabled', 'REU Preload Image': '/Usb0/preload.reu', 'REU Size': '2 MB', 'REU Preload Offset': '0 KB'}
[03] enable the Command Interface registers at $DF1B-$DF1F ... OK (0.068s)
[04] the command interface is idle once enabled ... OK (0.022s)
[05] transport: an empty command returns an empty reply ... OK (0.151s)
     <empty> -> 0.02s, data b'', status b''
[06] transport: unregistered target answers IDENTIFY ... OK (0.566s)
     0f 01 -> 0.06s, data b'NO TARGET', status b'00,OK'
[07] transport: unregistered target rejects anything else ... OK (0.644s)
     0f 7f -> 0.04s, data b'', status b'21,UNKNOWN COMMAND'
[08] transport: a no-reply command returns straight to Idle ... OK (0.126s)
[09] transport: pushing while a reply is pending sets and clears ERROR ... OK (0.877s)
[10] transport: the next command is not prefixed by leftover bytes ... OK (0.857s)
     04 28 00 -> 0.08s, data b'Ultimate 64 Elite', status b'00,OK'
[11] transport: ABORT releases a pending reply back to Idle ... OK (0.125s)
[12] control-target: IDENTIFY answers with the target name ... OK (0.834s)
     04 01 -> 0.05s, data b'CONTROL TARGET V1.1', status b'00,OK'
[13] control-target: an unimplemented command is reported as unknown ... OK (0.689s)
     04 7f -> 0.05s, data b'', status b'21,UNKNOWN COMMAND'
[14] control-target: LOAD_REU without a filename is rejected, not run ... OK (0.651s)
     04 08 -> 0.06s, data b'', status b'81,INVALID PARAMS'
[15] control-target: SAVE_REU without a filename is rejected, not run ... OK (0.657s)
     04 09 -> 0.05s, data b'', status b'81,INVALID PARAMS'
[16] control-target: GET_HWINFO rejects a device number it does not have ... OK (0.729s)
     04 28 02 -> 0.07s, data b'', status b'81,INVALID PARAMS'
     04 28 00 -> 0.07s, data b'Ultimate 64 Elite', status b'00,OK'
     04 51 -> 0.08s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[17] palette: GET_PALETTE returns 16 RGB colors ... OK (1.677s)
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[18] palette: SET_PALETTE_COLOR changes only the requested color ... OK (2.054s)
     04 53 0f e8 e8 e8 -> 0.16s, data b'', status b'00,OK'
     04 51 -> 0.04s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xe8\xe8\xe8', status b'00,OK'
[19] palette: color index 16 is rejected ... OK (0.713s)
     04 53 10 00 00 00 -> 0.12s, data b'', status b'81,INVALID PARAMS'
[20] palette: SET_PALETTE replaces all 16 RGB colors ... OK (2.761s)
     04 52 0b 30 55 7a 9f c4 e9 0e 33 58 7d a2 c7 ec 11 36 5b 80 a5 ca ef 14 39 5e 83 a8 cd f2 17 3c 61 86 ab d0 f5 1a 3f 64 89 ae d3 f8 1d 42 67 8c b1 d6 -> 0.73s, data b'', status b'00,OK'
     04 51 -> 0.06s, data b'\x0b0Uz\x9f\xc4\xe9\x0e3X}\xa2\xc7\xec\x116[\x80\xa5\xca\xef\x149^\x83\xa8\xcd\xf2\x17<a\x86\xab\xd0\xf5\x1a?d\x89\xae\xd3\xf8\x1dBg\x8c\xb1\xd6', status b'00,OK'
[21] palette: short SET_PALETTE payload is rejected ... OK (1.260s)
     04 52 00 00 00 f7 f7 f7 8d 2f 34 6a d4 cd 98 35 a4 4c b4 42 2c 29 b1 ef ef 5d 98 4e 20 5b 38 00 d1 67 6d 4a 4a 4a 7b 7b 7b 9f ef 93 6d 6a ef b2 b2 -> 0.68s, data b'', status b'81,INVALID PARAMS'
[22] palette: GET_PALETTE rejects a payload ... OK (0.670s)
     04 51 00 -> 0.08s, data b'', status b'81,INVALID PARAMS'
[23] palette: RESET_PALETTE restores the built-in C64 colors ... OK (2.000s)
     04 54 -> 0.06s, data b'', status b'00,OK'
     04 51 -> 0.05s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[24] palette: RESET_PALETTE rejects a payload ... OK (0.667s)
     04 54 00 -> 0.08s, data b'', status b'81,INVALID PARAMS'
[25] palette: restore the original runtime palette ... OK (2.681s)
     04 52 00 00 00 f7 f7 f7 8d 2f 34 6a d4 cd 98 35 a4 4c b4 42 2c 29 b1 ef ef 5d 98 4e 20 5b 38 00 d1 67 6d 4a 4a 4a 7b 7b 7b 9f ef 93 6d 6a ef b2 b2 b2 -> 0.74s, data b'', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[26] issue-740-matrix: confirm /Temp/uci_e2e_absent.reu does not exist ... OK (0.013s)
[27] issue-740-matrix: put a 16384-byte image at /Temp/uci_e2e_present.reu ... OK (0.123s)
[28] issue-740-matrix: REU Enabled, image absent, name at offset 2 ... OK (3.232s)
     04 08 52 45 55 2e 49 4d 47 -> 0.15s, data b"\xff\xff\xff\xffREU LOAD: FAILED TO OPEN /TEMP/UCI_E2E_ABSENT.REU: FILE DOESN'T EXIST", status b'85,REU FILE CANNOT BE OPENED'
[29] issue-740-matrix: REU Enabled, image absent, name at offset 4 ... OK (3.166s)
     04 08 00 00 52 45 55 2e 49 4d 47 -> 0.18s, data b"\xff\xff\xff\xffREU LOAD: FAILED TO OPEN /TEMP/UCI_E2E_ABSENT.REU: FILE DOESN'T EXIST", status b'85,REU FILE CANNOT BE OPENED'
[30] issue-740-matrix: REU Disabled, image absent, name at offset 2 ... OK (0.946s)
     04 08 52 45 55 2e 49 4d 47 -> 0.16s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[31] issue-740-matrix: REU Disabled, image absent, name at offset 4 ... OK (0.984s)
     04 08 00 00 52 45 55 2e 49 4d 47 -> 0.19s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[32] issue-740-matrix: REU Enabled, image present, name at offset 2 ... OK (2.570s)
     04 08 52 45 55 2e 49 4d 47 -> 0.16s, data b'\x00@\x00\x00REU LOAD: LOADED 16384 BYTES TO $000000 FROM /TEMP/UCI_E2E_PRESENT.REU', status b'00,OK'
[33] issue-740-matrix: REU Enabled, image present, name at offset 4 ... OK (2.641s)
     04 08 00 00 52 45 55 2e 49 4d 47 -> 0.20s, data b'\x00@\x00\x00REU LOAD: LOADED 16384 BYTES TO $000000 FROM /TEMP/UCI_E2E_PRESENT.REU', status b'00,OK'
[34] save-reu-offset-past-end: put the preload offset at the end of a 128 KB REU ... OK (0.092s)
[35] save-reu-offset-past-end: SAVE_REU reports the offset and saves nothing ... OK (2.735s)
     04 09 52 45 55 2e 49 4d 47 -> 0.15s, data b'\xfd\xff\xff\xffREU SAVE: OFFSET 131072 >= REU SIZE 131072, SKIPPING', status b'86,REU OFFSET > SIZE. NOT SAVED'
[36] load-reu-disabled: disable the REU ... OK (0.026s)
[37] load-reu-disabled: leave text in the reply buffer from a previous command ... OK (0.819s)
     04 28 00 -> 0.08s, data b'Ultimate 64 Elite', status b'00,OK'
[38] load-reu-disabled: command reports the REU is disabled and replies with 4 bytes ... OK (0.890s)
     04 08 52 45 55 2e 49 4d 47 -> 0.14s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[39] save-reu-disabled: disable the REU ... OK (0.031s)
[40] save-reu-disabled: leave text in the reply buffer from a previous command ... OK (0.806s)
     04 28 00 -> 0.07s, data b'Ultimate 64 Elite', status b'00,OK'
[41] save-reu-disabled: command reports the REU is disabled and replies with 4 bytes ... OK (0.899s)
     04 09 52 45 55 2e 49 4d 47 -> 0.17s, data b'\xfe\xff\xff\xff', status b'84,REU NOT ENABLED'
[42] softiec-single-part-reply: SoftIEC target answers IDENTIFY ... OK (0.998s)
     05 01 -> 0.06s, data b'SOFTWARE IEC TARGET V1.0', status b'00,OK'
[43] softiec-single-part-reply: LOAD_SU on a missing file completes in one part ... OK (0.437s)
     05 10 00 00 00 00 00 00 4e 4f 53 55 43 48 46 49 4c 45 -> 0.31s, data b'', status b'\x01'
[44] softiec-single-part-reply: GET_FATNAME reply completes in one part ... OK (0.428s)
     05 22 02 23 -> 0.09s, data b'/buffer', status b'\x00'
[45] softiec-single-part-reply: an unimplemented SoftIEC command is reported as unknown ... OK (0.207s)
     05 7f -> 0.06s, data b'', status b'\x04'
     get-drvinfo: /v1/drives lists [('a', 8, True), ('b', 9, False), ('IEC Drive', 11, False), ('Printer Emulation', 4, False)]
[46] get-drvinfo: GET_DRVINFO completes ... OK (0.663s)
     04 29 -> 0.05s, data b'\x04\x00\x08\x01\x00\t\x00\x0f\x0b\x00P\x04\x00', status b'00,OK'
[47] get-drvinfo: GET_DRVINFO with the current bus ids completes ... OK (0.700s)
     04 29 00 -> 0.07s, data b'\x04\x00\x08\x01\x00\t\x00\x0f\x0b\x00P\x04\x00', status b'00,OK'
[48] get-drvinfo: both forms send the same number of bytes ... OK (0.000s)
[49] get-drvinfo: the device count matches the bytes sent ... OK (0.000s)
[50] get-drvinfo: every device in /v1/drives has an entry ... OK (0.000s)
[51] softiec-x00-name: put a P00 file, and a file named like one, on the RAM disk ... OK (0.109s)
[52] softiec-x00-name: a full path to a P00 file answers the name in its header and PRG ... OK (0.595s)
     05 23 2f 54 65 6d 70 2f 55 43 49 47 41 4d 45 2e 50 30 30 -> 0.26s, data b'\x01MY GAME', status b'\x00'
[53] softiec-x00-name: the same file without its directory answers the host name ... OK (0.712s)
     05 23 55 43 49 47 41 4d 45 2e 50 30 30 -> 0.23s, data b'\x00UCIGAME.P00', status b'\x00'
[54] softiec-x00-name: a file without the signature answers its host name ... OK (0.824s)
     05 23 2f 54 65 6d 70 2f 55 43 49 50 4c 41 49 4e 2e 50 30 30 -> 0.31s, data b'\x00UCIPLAIN.P00', status b'\x00'
[55] softiec-setting-modes: IEC Drive Enabled: on the bus True, UCI True, $DF1B 11 ... OK (1.016s)
     05 01 -> 0.04s, data b'SOFTWARE IEC TARGET V1.0', status b'00,OK'
     on the bus True, $DF1B 11, IDENTIFY status b'00,OK'
[56] softiec-setting-modes: IEC Drive Disabled: on the bus False, UCI False, $DF1B 31 ... OK (0.231s)
     05 01 -> 0.05s, data b'', status b'\x05'
     on the bus False, $DF1B 31, IDENTIFY status b'\x05'
[57] softiec-setting-modes: IEC Drive UCI Only: on the bus False, UCI True, $DF1B 11 ... OK (1.063s)
     05 01 -> 0.06s, data b'SOFTWARE IEC TARGET V1.0', status b'00,OK'
     on the bus False, $DF1B 11, IDENTIFY status b'00,OK'
     04 51 -> 0.05s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
     04 51 -> 0.06s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[58] palette-stream: the firmware offers palette streams ... OK (0.011s)
[59] palette-stream: a refused palette value leaves the debug stream running ... OK (0.765s)
[60] palette-stream: palette=0 sends no palette packets ... OK (1.503s)
[61] palette-stream: a palette change on an ordinary stream sends nothing ... OK (2.154s)
     04 53 0f e8 e8 e8 -> 0.27s, data b'', status b'00,OK'
[62] palette-stream: an opted-in start sends the current palette at once ... OK (0.032s)
[63] palette-stream: the palette repeats once a second ... OK (1.999s)
[64] palette-stream: a palette change is sent at once ... OK (1.686s)
     04 53 0f 17 17 17 -> 0.20s, data b'', status b'00,OK'
[65] palette-stream: rapid changes are coalesced without stopping the video ... OK (10.3s SLOW)
     04 51 -> 0.28s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
     04 51 -> 0.10s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B#h\x8b\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
     240 changes in 1.08 s -> 41 palette packets at 40.2/s; 3883 video packets, 0 lost on the way here, longest gap 2 ms
[66] palette-stream: RESET_PALETTE reaches an opted-in client at once ... OK (3.981s)
     04 54 -> 0.10s, data b'', status b'00,OK'
     04 51 -> 0.11s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[67] palette-stream: a later ordinary start is not opted in ... OK (1.535s)
[68] palette-stream: palette packets still arrive after 20 opted-in starts and stops ... OK (5.214s)
[69] palette-stream: a stream to 230.0.1.64, below 232.0.0.0, is sent as multicast ... OK (1.135s)
[70] palette-stream: restore the original runtime palette ... OK (5.005s)
     04 52 00 00 00 f7 f7 f7 8d 2f 34 6a d4 cd 98 35 a4 4c b4 42 2c 29 b1 ef ef 5d 98 4e 20 5b 38 00 d1 67 6d 4a 4a 4a 7b 7b 7b 9f ef 93 6d 6a ef b2 b2 b2 -> 1.46s, data b'', status b'00,OK'
     04 51 -> 0.10s, data b'\x00\x00\x00\xf7\xf7\xf7\x8d/4j\xd4\xcd\x985\xa4L\xb4B,)\xb1\xef\xef]\x98N [8\x00\xd1gmJJJ{{{\x9f\xef\x93mj\xef\xb2\xb2\xb2', status b'00,OK'
[71] interface-usable-after: the control target still answers IDENTIFY ... OK (0.775s)
     04 01 -> 0.05s, data b'CONTROL TARGET V1.1', status b'00,OK'
     restored C64 and Cartridge Settings: {'Command Interface': 'Enabled', 'RAM Expansion Unit': 'Disabled', 'REU Preload Image': '/Usb0/preload.reu', 'REU Size': '2 MB', 'REU Preload Offset': '0 KB'}

--- summary
     transport: OK
     control-target: OK
     palette: OK
     issue-740-matrix: OK
     save-reu-offset-past-end: OK
     load-reu-disabled: OK
     save-reu-disabled: OK
     softiec-single-part-reply: OK
     get-drvinfo: OK
     softiec-x00-name: OK
     softiec-setting-modes: OK
     palette-stream: OK
     interface-usable-after: OK
     cleanup: OK
uci_targets_test: OK (71 checks, 95.9s)
uci-targets: OK (96.0s)

TEARDOWN (overlay): release input ... OK (0.017s)
TEARDOWN (overlay): close active menu UI ... OK (0.026s)
TEARDOWN (overlay): reset machine ... OK (0.055s)

settings: restore 192.168.1.148 ... OK (unchanged, 0.286s)

============================================================
Summary
============================================================
     OK   e2e overlay    96.0s (1 ok)
     96.0s in suites, 2.497s in health checks, the UI-state gate and teardown
     98.5s total

OK  all 1 suite runs passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3.16 Targeted for release 3.16. enhancement New functionality or an improvement to existing behavior, including formats and developer tooling. network Networking and remote services: REST, Socket DMA, FTP, Telnet, and online access. video VIC emulation, rendering, video timing, HDMI, and analogue video output.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants