NES on the dashboard, and mirroring the screen to the P4

Getting inside the MIB2 ended with a magenta rectangle. It was small, self-written and only stayed on screen for four seconds, yet it proved the factory unit could create a QNX Screen window, initialise EGL and swap a frame on the Vivante GPU.

The obvious question was whether it could run a game. The less obvious problem was getting a game-sized executable onto a system whose useful write location was a volatile QNX RAM filesystem reached through Telnet. On 14th September I got through both problems, then turned the same link around and used it to send the factory screen back to the P4 panel. The target is the same MIB2 Standard in the Scirocco: an i.MX6 running QNX 6.5 behind an 800 by 480 factory display. The P4 joins the head unit's hotspot, opens a bounded root Telnet session and removes every file it owns when a run finishes. No game is installed on the MIB, no factory HMI file changes and no SD-card write occur here.

A six kilobyte program first

RetroArch is a large step away from a magenta clear. Its first useful build had to call libc, Screen, EGL and GLES2 through the small ABI reconstructed from the unit's own runtime, load a libretro core, produce a native menu and exit again, so I sent a 6,096-byte probe before attempting the frontend; it wrote the exact success marker and returned zero, establishing the stat and time layouts, monotonic clock, stdio and ARM entry routine on this particular unit. That was the easy transfer. The first upload method sent escaped 64-byte blocks through shell commands. Its median cost was 432 ms per block. Projecting that onto the frontend produced a 2.83-hour upload, so I cancelled it at command 214 instead of letting an arbitrary timeout decide what an incomplete binary meant.

A native bulk receiver was faster in principle. It accepted numbered base64 batches over Telnet stdin and checked them before launch, yet the retained console traffic still limited it to about 1.8 KB/s; after 600 seconds it had accepted 1,083,392 bytes, hit its process alarm and stopped before reaching its full-file SHA-256 check. RetroArch never ran.

The compact route did finish: 1,509,136 bytes arrived and read back in 187.772 seconds. It failed in a more educational place. The first QNX frontend had an empty shader-backend table because its build omitted HAVE_GLSL, and it chose a platform default config path rather than the guarded config prepared for it. It created EGL and GLES2 successfully, then exited with the factory HMI still on screen. A transfer hash is evidence about a transfer. It does not turn a failed menu into a rendered menu.

Adding the stock GLSL renderer and binding the default path to the exact owned configuration fixed those two defects. The next 307-command run rendered the green RetroArch MAIN MENU through native QNX Screen, EGL 1.4 and GLES2 on the Vivante GC2000, captured it and exited zero; playlist-save attempts at teardown were denied by the application's own read-only wrappers, as intended for a bounded process that left no installed frontend behind.

The first native NES frame

The first core test used a CC0 NROM program that draws a pink and blue checkerboard, chosen because every output pixel was known before the MIB ran it. FCEUmm delivered 240 non-null video callbacks in 3.970996 seconds with one emulated frame per callback and no requested frameskip, a measured cadence of 60.1864 emulated frames per second against a nominal 60.0998: 100.144 percent.

That number needed a warning attached. A core callback includes downstream video work; it is not a physical display presentation. The short capture run had a mean interval of 16.615 ms, a p99 of 32 ms and a worst interval of 33 ms. Audio and input were null. The checkerboard established that a native emulated scene could arrive at the expected core cadence. It did not establish smooth scanout, controller latency or game compatibility.

Measured NES core cadence and 400 by 240 mirror progression. Core callbacks and P4 presentation submissions are different measurements.

The next test needed something that could demonstrate progression. Alter Ego's first level begins with a HELLO WORLD screen, then has platforms, ladders, characters and water; a 900-frame run returned 900 nonduplicate callbacks while the core polled input 900 times and received four scripted Start presses plus thirty scripted Right presses, enough to enter and move through the level before the P4 presented a 400 by 240 RGB565 gameplay frame that I confirmed physically.

That sample ran for 14.410 seconds at 103.806 percent of nominal core speed, with a 16.029 ms mean interval, 17 ms p99 and one 47 ms worst interval. It was slightly fast, the controller was still a script and audio remained disabled. The run showed real game code advancing. Its execution and one static P4 frame did not establish a final 60 Hz presentation path or repeatable play under a physical controller.

FTP removed the long wait

The real breakthrough was not a faster shell command. QNX had an FTP server and its RAM filesystem accepted a flat, exclusive filename. The P4 could take a binary from the Mac over its USB serial link, upload it by FTP, retrieve it in full and compare the stored SHA-256 before any launch was admitted.

For the 2,848,476-byte RetroArch executable, Mac to P4 ingress took 32.777 seconds, the P4 to MIB FTP transfer took 1.699 seconds and full retrieval plus hash verification took another 2.131 seconds; the complete operation, including identity checks, boot, association, transfer start and final hash check, took 57.410 seconds at a measured network upload rate of 1.677 MB/s, about 210 times the earlier approximate Telnet baseline.

The upload retained its RAM file for the next bounded launch, then the launch package verified it again. No transfer was treated as evidence that the file would survive a reset. This was volatile delivery, not a package manager.

The prepared game library was in physical SD2. A read-only session matched its card marker, JSON index and Alter Ego ROM through the final bytes and SHA-256, giving the frontend a verified file handle without copying the game to the navigation card; the first minute-long run had the right speed and also exposed a configuration-name mismatch plus an unproven audio initialisation path.

The corrected run loaded the exact 646-byte configuration, used the SD2 ROM and executed 3,600 core frames in 59.884940115 seconds, 99.9979297 percent of its nominal rate; the p99 core-call interval was 16.99998 ms, the worst was 17.99998 ms, no video callback duplicated and every EGL swap returned success. Those results describe one minute of core and API behaviour. Scanout and CPU remained unknown. Long sessions, audible output and a physical controller still needed separate tests.

Reading the factory screen from the other side

Games made the MIB's display useful. Mirroring made it inspectable. The native helper reads the QNX Screen buffer, JPEG-encodes each frame and sends complete datagrams to the P4, which decodes and scales them into its 720 by 720 panel. The P4 has hardware JPEG and a pixel-processing accelerator, so the first design looked straightforward. It stopped at the first self-test. A tiny 48 by 16 baseline JPEG timed out in the hardware driver inside its 50 ms bound, once for each output component order. The MIB helper was never launched. That result did not say that Wi-Fi, QNX Screen or the capture path had failed. A sibling package added a fixed-memory software decoder for a normal 400 by 240 image and kept the hardware decoder as an explicitly tested option.

The first continuous result decoded and presented 247 frames with no JPEG errors. One-second windows reached roughly 26 to 29 fps at 400 by 240. Encoding cost about 35.5 ms, P4 decode about 1.6 ms and P4 scaling plus rendering about 25 to 30 ms. The hotspot then lost beacons. I recorded the disconnect as a radio observation rather than guessing why it happened.

R3 found that reading the mapped Screen buffer pixel by pixel was expensive. A reusable RAM copy roughly halved encode cost. R4 measured that change across nine mode and quality combinations. At 400 by 240 and quality 30, a 60 fps trial reached 53.36 P4 presentation submissions per second. A later ten-second window reached 29.81 fps at 30 fps, while an 81.175 ms estimated frame age triggered the predefined downgrade to 15 fps. The stream was improving, yet the measured windows still fell well short of stable 60 fps delivery.

R5 added an ARM NEON encoder. The 400 by 240, 60 fps, quality 30 trial reached 56.89 submissions per second. It missed the strict 95-percent-of-60 threshold and was rejected as a sustained 60 Hz candidate. The selected 30 fps mode then returned 29.96, 29.74 and 29.98 fps across three ten-second validation windows, with no downgrade. The end-to-end video path had its first sustained half-minute of roughly 30 fps operation.

The last problem was not raw speed. The receiver could hold a completed frame while fragments from a newer frame arrived, then accidentally replace it with an older buffered result. R8 preserved an immutable held frame during that exact regression and replaced stale work only with newer completed work. In the live run, the P4 submitted 1,109 frames over 20.316249 seconds, 54.59 fps, with zero decode or presentation errors. Mean capture-start to presentation-submission age was 53.477 ms and the maximum was 93.691 ms, with clock uncertainty recorded.

I watched current MIB motion on the P4 without freezes or old frames returning. That is physical evidence for this bounded 400 by 240 live stream. The 54.59 fps result remains below sixty, the age is an estimate rather than glass-to-glass latency and the run says nothing about full-resolution video or long-session stability.

By the end of 14th September, a native NES core could run from the MIB's SD2 library at nominal cadence, its frontend could arrive over verified FTP in under a minute, and the factory display could move across to the P4 in real time. The next work was no longer basic ingress or graphics bring-up. It was finding a safe place in the factory HMI for the launcher and getting a real USB controller through the MIB's input stack.