Choosing emulator cores by running every game on the card
Clutch Cargo turns the MIB2 head unit in my Volkswagen Scirocco into a games machine: QNX 6.5 on an i.MX6 Cortex-A9, an 800 by 480 dashboard panel, an ES-DE frontend I cross-compiled for it, and each emulator running as its own child process. Since eleven emulators and N64 refusing to co-operate, the library on the card has grown to about 7,500 titles, and several of the systems it depends on were running cores that came with Rusty Nail, my fantasy console for the ESP32-P4. They were written for a microcontroller, and it showed.
On the same day as this work, the Master System core faulted on every launch because its fast Z80 path read the Cortex-M debug cycle counter at 0xE0001004, an address that exists on a microcontroller and is unmapped on the head unit. The Mega Drive core had refused 92 of 479 titles on a stack pointer check the real hardware never makes. The Game Boy core refused, panicked or stalled on 153 titles in a capture run on the Mac. Amiga Lemmings ran with 703 overruns in 2,749 frames. Each was fixable. Together they said the cores had been ported without asking how they compared with the ones everyone else uses.
Starting from what the handhelds use
My handheld runs Knulli, which is built on Batocera, and its default core for each system lives in a YAML file in the distribution repository (configgen-defaults.yml on the knulli-main branch). Gambatte handles Game Boy and Game Boy Color, Genesis Plus GX the Mega Drive, Master System and Game Gear, fake-08 PICO-8, and PUAE the Amiga. A per-chip override for the Allwinner H3, a quad Cortex-A7, swaps the Mega Drive and Master System to PicoDrive and the Amiga to Amiberry. The H3 sits nearer this head unit than the handheld's Cortex-A53 does, so that override mattered.
I treated the list as candidates rather than answers. The card holds my library, with a long tail of prototypes and unlicensed Taiwanese carts, and the only honest test was to run all of it.
A sweep, and two wrong rules
I built each candidate as a Mac libretro core and wrote a runner that feeds one title at a time through a headless host for 1,200 frames, pressing Start at frames 300 and 700. The first rule was simple: a title failed if the screen was one flat colour at frames 600 and 1,199.
Turrican failed. It is a licensed Game Boy release that certainly works, so I saved its frames. It drew the Nintendo logo, then the Accolade logo, and after the Start press it sat on a white screen, which is a legitimate state for a game waiting on its next input. The rule was judging a picture by two samples.
The second rule sampled eight frames across the run and called a title blank only if every one was flat. SameBoy first refused every title, which turned out to be my runner looking for its library in the wrong directory, and once that was fixed it "rescued" 148 of the 163 titles Gambatte had failed. I laid their last frames out on a sheet and most were flat white or black: pirate carts that flash a logo, then hang. A game drawing once is a weak test for working.
The rule that stuck calls a title stalled when fewer than four of the eight samples drew anything and the last frame is flat. It still only catches games that never draw or never load; a game that plays wrongly passes.

Game Boy: accuracy against speed
Gambatte ran 2,697 of 2,851 Game Boy and Game Boy Color titles and SameBoy 2,718. Nearly every failure on either core is an unlicensed, pirate or prototype cart. SameBoy ran five licensed titles Gambatte did not: Aretha II, Command Master, Magic Knight Rayearth 2nd, and two that need hardware the car will never have, the tilt sensor in Kirby Tilt 'n' Tumble and the Game Boy Camera. Gambatte ran two unlicensed titles SameBoy missed. The Rusty Nail core's 153 failures came from a different rule, so I did not set that number against these.
Accuracy costs cycles, and headless on the Mac the difference is large: Gambatte ran Pokemon Crystal, Wario Land 3 and Super Mario Land at 7,217 to 8,217 frames per second, SameBoy at 786 to 1,143.
A Mac number means nothing until it is tied to the car. The ESP32-P4 on the car's side had just become a router between the Mac's Ethernet and the head unit's Wi-Fi hotspot, so FTP and Telnet reached the unit directly. I wrote a small benchmark that links the real core archive built for QNX, loads one ROM from RAM, runs 1,800 frames with no display, pad or audio, and reports the rate. It went up to /dev/shmem with the ROMs, ran over Telnet, and was deleted afterwards.
CC_BENCH core=Gambatte version=v0.5.0 frames=1800 video=1799 audio_frames=987982
elapsed_us=3616996 mean_us=2009 worst_us=8000 fps_x10=4976 target_fps_x10=597
That is Pokemon Crystal at 498 frames per second, 2.0 ms per frame against a budget of 16.7. Wario Land 3 ran at 522 and Super Mario Land at 840. The Mac turned out to be between 9.8 and 14.7 times faster than the head unit depending on the game, and at the slow end of that range SameBoy would manage about 54 fps on Wario Land 3. That is short of 60, and it settled Game Boy on Gambatte.

Sega: taking the slower core
PicoDrive ran 1,145 of the 1,155 Mega Drive, Master System and Game Gear titles, Genesis Plus GX 1,152. I checked PicoDrive's failures by eye because of Turrican. Exo Squad plays and was a sweep artefact. WWF WrestleMania Arcade, Micro Machines 2 and both World Series Baseball titles on Game Gear, and the Korean Nemesis do fail. Genesis Plus GX lost no licensed title; its three misses were The Revenge of Shinobi (a black intro both cores show), an unlicensed Chinese cart and a prototype.
PicoDrive is the faster core, two to three times faster on Mega Drive games even with its C CPU cores on the Mac, and that is why Knulli picks it for the H3. Divided by the Gambatte ratio, though, Genesis Plus GX lands near 200 fps on the head unit, and a core that clears 60 with three times to spare loses nothing by being slower. I chose Genesis Plus GX for all three systems, one binary with the system picked from the file extension. That figure is an estimate. The car was switched off before its own benchmark could run.
fake-08 ran 1,060 of 1,067 PICO-8 carts; five timed out and two never drew. Rusty Nail's PICO-8 core has no Mac libretro build, so there is no like-for-like count for it.
An old C++ library on the unit
The toolchain is GCC 9.3, but the head unit ships its own libstdc++.so.6 from GCC 4.4, and that is what a C++ core links against at run time. Gambatte needed one function from the newer library, __throw_out_of_range_fmt, which the host now supplies; with exceptions off, it ends the process. fake-08 asked for more. It is C++17 and builds its Lua's C files as C++, so Lua raised errors with C++ exceptions until LUA_USE_LONGJMP put it back on setjmp and longjmp. It converts its charset table with std::wstring_convert<std::codecvt_utf8<char32_t>>, and GCC 4.4 has no char32_t codecvt at all. Rather than supply that class and everything behind it, I patched the one call with a small UTF-8 decoder that produces the same u32string, and compiled the few other newer pieces (list node operations, system_clock::now) into the core's archive.
The finding that would have hurt was in fake-08's file handling. It writes cart data, settings and its log like this:
FILE * file = freopen(getCartDataFile(cartDataKey).c_str(), "w", stderr);
if( file != NULL ) {
//Initialize data
fprintf(file, "%s", contents.c_str());
fflush(file);
fclose(file);
}
On a desktop that quietly redirects a stream nobody reads, and nothing is lost. Each emulator on the head unit writes its diagnostics to descriptor 2 and the session keeps them as the run log, so the first time a PICO-8 game saved, the host's own log lines would have landed in the save file, and the fclose would then have ended the log for the rest of the run, exit line included. The build rewrites all five of those calls as plain fopen, and asserts it found exactly five.
Checked so far
Each new host has a small C boundary between the ClutchOS shell and the libretro core, and I compiled those against the Mac builds of the same cores so that the host's own code could run seven titles on the Mac. Gambatte gave 160 by 144 at 32,768 Hz and exposed Tetris DX's 8 KiB of cartridge RAM. Genesis Plus GX gave 320 by 224 for Sonic 2, 256 by 192 for Phantasy Star and 160 by 144 for Columns, at 44,100 Hz, and fake-08 gave 128 by 128 at 22,050 Hz. Every one drew a changing picture for 900 frames. The three hosts link for QNX and pass the host policy check, which refuses imports such as remove, rename and system.
None of it has run in the car. The package is built and waiting for the card to come back to the Mac. Sound, the pad mapping and saves are unproven on the unit, and saves written by the old cores keep their paths but may not match the new cores' sizes. The real-time clock that Pokemon Gold, Silver and Crystal keep on the cartridge is not saved between launches. Amiga still runs the old core, since both real replacements are sizeable ports and the card holds one Amiga game.
Next is the card: install the package, then a Game Boy and a Game Boy Color game with a save in Pokemon Crystal, two Mega Drive games, a Master System and a Game Gear game, and two PICO-8 carts. When the ignition is next on, the Genesis Plus GX and fake-08 benchmarks run on the head unit to replace the estimate with a measurement.