Run Mesen2's `--testRunner` mode in CI, point it at a NEScript-built
ROM + auto-loaded .dbg, and assert via Lua that the four entry-point
labels (`nmi`, `irq`, `Main_frame`, `main_loop`) we promised to emit
actually resolve. Failures encode which assertion broke into the exit
code so CI can report it without stdout (Mesen's emu.log is internal).
The setup needed three workarounds, each documented inline in the
workflow:
* `touch settings.json` to skip Mesen's first-run GUI wizard, which
blocks the --testRunner dispatch in Program.cs:74. Contents don't
matter — Configuration.Deserialize falls back to defaults on parse
error.
* `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1` to keep .NET from loading
system libstdc++ via libicuuc. On Ubuntu 24.04 the dual libstdc++
presence (system + MesenCore.so's bundled static copy) crashes
MesenCore's static regex initialiser with std::bad_cast before any
user code runs.
* `xvfb-run` because Mesen's Avalonia UI calls XOpenDisplay before
--testRunner is dispatched.
This is a separate workflow file from ci.yml because it depends on the
40 MB Mesen2 binary download + xvfb + sdl2, none of which the existing
jobs need. Cached by Mesen version so reruns are fast.
https://claude.ai/code/session_01DfN3pKJLryr7vvNFBpcqmC