Porting ES-DE to ARM QNX
Eleven emulators, and N64 refusing to co-operate left Rusty Nail with several working core paths, but its compact library was still a launcher experiment; I wanted the car to have a browser for the whole card, using ES-DE's interaction model and Art Book Next's restrained system and game views to make a large collection readable on an 800 by 480 panel.
The target was still the MIB2 Standard in my Scirocco: an i.MX6 running QNX 6.5; I wanted a deliberately small slice of the upstream application, a browser that read a known card, drew one view, accepted one USB controller, started one bounded game child, and rebuilt a fresh browser after that child returned without carrying graphics, input, or file state from the old process. Scraping, audio, saves, open-ended runs and a general storage API stayed outside the work.
The card came first
The collection could not become a library until it was somewhere the launcher could find it without damaging the existing card; I inventoried 13,486 selected files, including each planned system destination and known format blockers, confirmed the source scan had no read errors, retained archive overlaps as separate verified ZIPs, and left unusual N64 filename extensions alone because header detection, rather than a renamed filename, identified their byte order.
The first destination card copy was stopped cleanly after 115 verified files. That was useful: it exercised the interrupted state without leaving a partial library presented as complete. I backed up and verified the original 1,544 files, prepared a spare SanDisk Extreme, restored those originals, then copied the imported collection one file at a time, synchronising every write before reading the result back against its recorded checksum. The final receipt recorded 13,486 imported files and 41.67 GiB. The copy took 2,122.33 seconds, averaging 19.63 MB/s across the whole write and verification process. That number describes this guarded migration, including the checks; it does not describe the card's raw throughput. The temporary Mac cache went away only after the card and the original-file preservation checks had passed.
The first visible library was smaller than that card population. The QNX launcher could admit only systems and layouts it understood, while the larger collection kept its own compatibility backlog. A copied title was a checked file on a card. It was not a claim that its emulator would load it, run it at speed, produce audio or save.

Upstream was a boundary, not a build button
ES-DE itself brought a large C++17 surface: SDL2 plus image and font libraries, XML, filesystem assumptions, process launching and an upstream GLES3 renderer. QNX had a useful SDL Screen backend, but that did not make the application portable. The MIB had a proven GLES2 path, and the target C++ and graphics interfaces needed their own evidence.
I started with an audit instead of a broad port. One unchanged filesystem translation unit compiled for ARM/QNX, but its object had 156 undefined symbols. The captured target libraries supplied names for 116 of them. The remaining group included 34 C++ runtime symbols, most related to filesystem work. A matching symbol name was only an invitation to investigate; it did not establish runtime ABI compatibility. I had to know whether constructor and destructor behaviour, allocation, exception handling, thread use, and filesystem types would survive the target's particular C++ boundary before a full system browser could be a responsible target-side experiment.
The earliest runtime closure was a small harness rather than a frontend, designed to make a real target loader, C++ runtime and filesystem boundary fail visibly before I asked the graphics stack, theme parser and game-process lifecycle to depend on the same fragile assumptions. Eight source-built GNU support units let it link at 51,936 bytes, with 81 final dynamic imports assigned to captured providers. Host sanitiser checks covered 213 filesystem cases and a real host C++ runtime exercise. No theme, target window, card write or native execution happened in that stage. The XML work followed the same rule. The pinned parser handled the Art Book Next corpus: 251 XML files and 41,680 DOM nodes under host sanitizers. A small ARM/QNX buffer probe linked against libc only. The test did not decode images, evaluate the complete theme, or prove that the MIB could create the frontend window. Each small component reduced a known gap without pretending to finish the port.
A browser with limits
The reduced application used an explicit system configuration and a read-only library model that could show system views, game lists, detail pages plus favourites and custom collections, while keeping all card discovery inside a small enough contract that a malformed entry had a defined refusal path rather than an opportunity to become a browser crash. I bounded its card traversal, rejected symlinks and unsafe entries, and admitted a launch only after its system, exact path, recorded size and hash matched a registered executable.
Large libraries exposed another cost. Eight thousand rows did not need eight thousand shaped text objects at once. The final list retained at most 64 text components per list and recreated rows as selection moved, so a system with thousands of titles no longer asked the font stack to shape every hidden row before the first visible one could be drawn. The 8,000-game host run reached a first frame in 6,614 ms, with a recorded peak RSS of 974,204 KiB. Those were sanitised host observations, after theme discovery, and they gave no estimate of native startup or MIB memory.
I also expanded the model to 8,192 source games across six systems, with an explicit refusal at 8,193 so an accidental library growth could not move memory pressure from a documented boundary into an unpredictable native failure during a scan. Host runs covered an 8,193-game refusal, late-title selection, favourites, custom collections and rendered Art Book Next views. The number was an application capacity boundary. It did not establish that the card currently held exactly that many eligible titles or that every listed title could launch.
The design kept the existing native game hosts outside ES-DE. On selection, the browser released its UI and controller resources before starting one checked child. The parent reaped that child, restored a bookmark into a new browser process, and refused uncertain ownership. A failed game returned a notice; it did not make the parent guess whether a process was still running.
The first screen failed twice
The initial card delivery was deliberately browsing-only, because the first question was whether the real QNX runtime could open the theme and draw a screen without confusing a browser failure with an emulator or controller failure. It carried 1,637 files, each read back by SHA-256, and added one separate menu entry while preserving the existing menu and runtime files. The launcher copied its executable into volatile storage, checked it before use, and gave itself finite deadlines. It could not start an emulator.
The first physical selection failed before the native binary started, because QNX shared memory did not accept the directory the launcher tried to create; the correction used flat shared-memory file names, exclusive creation and hash checks, while eleven host launcher cases covered the original directory failure, lock preservation, tampering and the corrected execution shape. That was still host evidence, so I re-imported the menu and ran the same entry again.
Run one reached window setup and library enumeration, then exited with no themes and a directory-enumeration failure. I first widened the filesystem metadata calls, because the target's large metadata was a plausible explanation. Run two proved that theory incomplete. A strange three-character child name caused a metadata lookup to fail even though the browser would later ignore that name.
The fix was deliberately narrow: nonrecursive enumeration returns child names without requiring metadata for each one, while recursive scans remain strict and the existing limits for names, entry count, and symlink handling stay in place, preventing one ignorable child from cancelling a normal list without relaxing the safety checks that protect a full traversal. The host regression injected the exact error path. The next native run finally rendered the Art Book Next screen.
That corrected preview recorded 231 frames, 362,317 nonblack pixels in a sampled full-resolution frame, exit zero and complete browser cleanup, leaving the target's pre-existing factory processes in place and showing that the actual QNX build had created its window, loaded the theme, presented a frame and completed final cleanup rather than merely linking on the host. I then watched the head unit and confirmed that the theme appeared and returned normally. The result proved the exercised render and presentation path. It did not prove touch, 60 Hz behaviour, game launching or permanent installation.
A controller report can hide in plain sight
The first USB controller run connected to the existing reader and received 1,501 reports. None became a browser action. The report stream was real, while navigation was absent.
The arming gate expected every button byte to be zero, but the adapter's ordinary acknowledgement retained one unmapped bit, so the browser waited for a neutral report that this controller would never send even though the underlying reader had connected, completed its established handshake and was continuously delivering reports to the application. I changed the gate to ignore that unmapped bit and left the reader protocol alone.
The next target run received 1,474 reports and routed eight button edges, enough for me to open a list, move selection, go back, then hold HOME for one second; the frontend rendered 308 frames, completed reader shutdown, cleaned up its resources and exited normally. That validates this controller and this navigation path. It does not validate cable removal, every controller, sustained performance or game return.
SMB3, then the factory menu
The existing SMB3 backend was the first browser game test, with ES-DE stopping its reader, releasing its UI, checking the selected title and starting the fixed child only after every one of those ownership boundaries had completed; the first trial reached the backend but exposed two different problems, the game needed the established volatile working directory and QNX process replacement crashed while returning through graphics ownership.
I gave the child the known working directory and changed the return boundary to one fresh worker process. It restored the selected SMB3 bookmark without replaying launch input. The visible flow worked, although the first successful game attempt still hit its 40-second supervisor while it emitted per-frame diagnostics after HOME. That run proved the selection and return shape; it did not prove a normal game shutdown.
Moving the output into an owned RAM log removed the live serial backpressure from the game exit, allowing the next observed run to detect HOME after 1,057 input polls, return from the core after 1,056 video callbacks, and release child ownership without the timeout that had previously interrupted diagnostic output. The fresh return worker restored SMB3, rendered 294 frames, then exited on HOME. The observed return was quicker, though I did not measure its exact latency.
The persistent menu entry added one final boundary. It could start the bounded browser, SMB3 and the navigation-only return view from the card, archive its native log, and remove its own temporary files afterwards. A GEM launch initially inherited lifecycle signals that the return guard rejected. A small startup normalisation for the application's expected lifecycle signals corrected that case without changing the factory processes.
The final observed GEM round trip opened ES-DE, launched SMB3, accepted HOME, restored the selection, and returned to ES-DE, with the first view rendering 58 frames, SMB3 completing 626 core calls and 625 video callbacks before normal exit, and the return view rendering 497 frames before the card launcher finished with no owned process or temporary path left behind.
This was a bounded 40-second game test with a 15-second browser interval and no second game launch from the return view. It was enough to prove one persistent menu path through the real hardware. Longer runs, repeated launches, transition flashes, touch, audio, broader game registration and native performance were still open when this work ended.